루프 중에 TextBox.Text에 추가하면 반복 할 때마다 더 많은 메모리를 차지하는 이유는 무엇입니까?
짧은 질문
180,000 번 실행되는 루프가 있습니다. 각 반복이 끝날 때 실시간으로 업데이트되는 TextBox에 결과를 추가해야합니다.
를 사용 MyTextBox.Text += someValue
하면 응용 프로그램이 엄청난 양의 메모리를 사용하게되며 수천 개의 레코드를 사용하면 사용 가능한 메모리가 부족합니다.
TextBox.Text
180,000 번에 텍스트를 추가하는 더 효율적인 방법이 있습니까?
편집 나는이 특정 경우의 결과에 대해 정말로 신경 쓰지 않지만 이것이 메모리 돼지 인 것처럼 보이는 이유와 TextBox에 텍스트를 추가하는 더 효율적인 방법이 있는지 알고 싶습니다.
긴 (원본) 질문
CSV 파일의 ID 번호 목록을 읽고 각각에 대한 PDF 보고서를 생성하는 작은 앱이 있습니다. 각 pdf 파일이 생성 된 후에 ResultsTextBox.Text
는 처리되고 성공적으로 처리 된 보고서의 ID 번호가 추가됩니다. 프로세스는 백그라운드 스레드에서 실행되므로 ResultsTextBox는 항목이 처리 될 때 실시간으로 업데이트됩니다.
현재 180,000 개의 ID 번호에 대해 앱을 실행하고 있지만 시간이 지남에 따라 응용 프로그램이 차지하는 메모리가 기하 급수적으로 증가하고 있습니다. 약 90K에서 시작하지만 약 3000 개 레코드가 약 250MB를 차지하고 4000 개 레코드가 애플리케이션이 약 500MB의 메모리를 차지합니다.
Results TextBox에 대한 업데이트를 주석 처리하면 메모리가 약 90K에서 비교적 고정되어 있으므로 쓰기 ResultsText.Text += someValue
가 메모리를 먹는 원인 이라고 가정 할 수 있습니다 .
제 질문은 왜 이래요? 메모리를 차지하지 않는 TextBox.Text에 데이터를 추가하는 더 좋은 방법은 무엇입니까?
내 코드는 다음과 같습니다.
try
{
report.SetParameterValue("Id", id);
report.ExportToDisk(ExportFormatType.PortableDocFormat,
string.Format(@"{0}\{1}.pdf", new object[] { outputLocation, id}));
// ResultsText.Text += string.Format("Exported {0}\r\n", id);
}
catch (Exception ex)
{
ErrorsText.Text += string.Format("Failed to export {0}: {1}\r\n",
new object[] { id, ex.Message });
}
또한 앱이 일회성이며 모든 보고서를 생성하는 데 몇 시간 (또는 며칠)이 걸리는 것은 중요하지 않다는 점도 언급 할 가치가 있습니다. 내 주요 관심사는 시스템 메모리 제한에 도달하면 실행이 중지된다는 것입니다.
이 작업을 실행하기 위해 주석 처리 된 결과 TextBox를 업데이트하는 줄을 그대로 두어도 괜찮지 만 TextBox.Text
향후 프로젝트를 위해에 데이터를 추가하는 메모리 효율적인 방법이 있는지 알고 싶습니다 .
메모리 사용량이 너무 큰 이유는 텍스트 상자가 스택을 유지하여 사용자가 텍스트를 실행 취소 / 다시 실행할 수 있기 때문이라고 생각합니다. 해당 기능은 귀하의 경우에 필요하지 않은 것 같으므로 IsUndoEnabled
false로 설정하십시오 .
사용 TextBox.AppendText(someValue)
대신에 TextBox.Text += someValue
. TextBox.Text가 아닌 TextBox에 있기 때문에 놓치기 쉽습니다. StringBuilder와 마찬가지로 무언가를 추가 할 때마다 전체 텍스트의 복사본을 만들지 않습니다.
이것이 IsUndoEnabled
keyboardP의 대답 의 플래그와 어떻게 비교되는지 보는 것은 흥미로울 것입니다.
text 속성에 직접 추가하지 마십시오. 추가를 위해 StringBuilder를 사용하고 완료되면 .text를 stringbuilder의 완성 된 문자열로 설정합니다.
텍스트 상자를 사용하는 대신 다음을 수행합니다.
- 만일을 대비하여 텍스트 파일을 열고 오류를 로그 파일로 스트리밍하십시오.
- 목록 상자 컨트롤을 사용하여 오류를 나타내면 잠재적으로 큰 문자열이 복사되지 않도록합니다.
개인적으로 저는 항상 string.Concat
*를 사용 합니다. 나는 일반적으로 사용되는 방법을 비교하는 프로파일 링 통계를 가지고있는 Stack Overflow에 대한 질문을 몇 년 전에 읽은 것을 기억 string.Concat
합니다.
그럼에도 불구하고 내가 찾을 수있는 가장 좋은 것은 이 참조 질문 과 내부적으로 를 사용하는 것을 언급하는 이 특정 String.Format
대StringBuilder
질문 입니다. 이것은 당신의 기억력이 다른 곳에 있는지 궁금하게 만듭니다.String.Format
StringBuilder
** James의 의견에 따르면 웹 기반 개발에 중점을두기 때문에 무거운 문자열 형식을 지정하지 않는다는 점을 언급해야합니다. *
아마도 TextBox를 재고할까요? String Items를 보유한 ListBox가 더 잘 수행 될 것입니다.
But the main problem seem to be the requirements, Showing 180,000 items cannot be aimed at a (human) user, neither is changing it in "Real Time".
The preferable way would be to show a sample of the data or a progress indicator.
When you do want to dump it at the poor User, batch string updates. No user could descern more than 2 or 3 changes per second. So if you produce 100/second, make groups of 50.
Some responses have alluded to it, but nobody has outright stated it which is surprising. Strings are immutable which means a String cannot be modified after it is created. Therefore, every time you concatenate to an existing String, a new String Object needs to be created. The memory associated with that String Object also obviously needs to be created, which can get expensive as your Strings become larger and larger. In college, I once made the amateur mistake of concatenating Strings in a Java program that did Huffman coding compression. When you're concatenating extremely large amounts of text, String concatenation can really hurt you when you could have simply used StringBuilder, as some in here have mentioned.
Use the StringBuilder as suggested. Try to estimate the final string size then use that number when instantiating the StringBuilder. StringBuilder sb = new StringBuilder(estSize);
When updating the TextBox just use assignment eg: textbox.text = sb.ToString();
Watch for cross-thread operations as above. However use BeginInvoke. No need to block the background thread while the UI updates.
A) Intro: already mentioned, use StringBuilder
B) Point: don't update too frequently, i.e.
DateTime dtLastUpdate = DateTime.MinValue;
while (condition)
{
DoSomeWork();
if (DateTime.Now - dtLastUpdate > TimeSpan.FromSeconds(2))
{
_form.Invoke(() => {textBox.Text = myStringBuilder.ToString()});
dtLastUpdate = DateTime.Now;
}
}
C) If that's one-time job, use x64 architecture to stay within 2Gb limit.
StringBuilder
in ViewModel
will avoid string rebindings mess and bind it to MyTextBox.Text
. This scenario will increase performance many times over and decrease memory usage.
Something that has not been mentioned is that even if you're performing the operation in the background thread, the update of the UI element itself HAS to happen on the main thread itself (in WinForms anyway).
When updating your textbox, do you have any code that looks like
if(textbox.dispatcher.checkAccess()){
textbox.text += "whatever";
}else{
textbox.dispatcher.invoke(...);
}
If so, then your background op is definitely being bottlenecked by the UI Update.
I would suggest that your background op use StringBuilder as noted above, but instead of updating the textbox every cycle, try updating it at regular intervals to see if it increases performance for you.
EDIT NOTE:have not used WPF.
You say memory grows exponentially. No, it is a quadratic growth, i.e. a polynomial growth, which is not as dramatic as an exponential growth.
You are creating strings holding the following number of items:
1 + 2 + 3 + 4 + 5 ... + n = (n^2 + n) /2.
With n = 180,000
you get total memory allocation for 16,200,090,000 items
, i.e. 16.2 billion items
! This memory will not be allocated at once, but it is a lot of cleanup work for the GC (garbage collector)!
Also, bear in mind, that the previous string (which is growing) must be copied into the new string 179,999 times. The total number of copied bytes goes with n^2
as well!
As others have suggested, use a ListBox instead. Here you can append new strings without creating a huge string. A StringBuild
does not help, since you want to display the intermediate results as well.
'development' 카테고리의 다른 글
ImportError : OS X에서 Python을 업그레이드 한 후 pkg_resources라는 모듈이없는 이유는 무엇입니까? (0) | 2020.09.24 |
---|---|
$ _SERVER [ 'REMOTE_ADDR']을 신뢰하는 것이 안전합니까? (0) | 2020.09.24 |
Windows 스토어 앱에서 CoreDispatcher를 가져 오는 올바른 방법 (0) | 2020.09.24 |
JsonMappingException : START_ARRAY 토큰에서 벗어남 (0) | 2020.09.24 |
git : 치명적 이메일 주소를 자동 감지 할 수 없음 (0) | 2020.09.24 |