2009-04-30 3 views
6

~ 120 MB의 일반 텍스트 CSV 파일을 읽는 데 C#을 사용하고 있습니다. 처음에는 구문 분석을 한 줄씩 읽음으로써 수행했지만, 최근에는 전체 파일 내용을 메모리에 먼저 읽는 것이 여러 번 빨라 졌다고 판단했습니다. CSV에 따옴표 안에 쉼표가 포함되어 있기 때문에 파싱이 이미 상당히 느립니다. 즉, 정규식 분할을 사용해야합니다. 메모리에 전체 내용을 읽은 후 구문 분석을 수행하기 위해120 MB CSV 파일의 String.Split()에 대한 .NET System.OutOfMemoryException

string[] fields = Regex.Split(line, 
@",(?!(?<=(?:^|,)\s*\x22(?:[^\x22]|\x22\x22|\\\x22)*,) 
(?:[^\x22]|\x22\x22|\\\x22)*\x22\s*(?:,|$))"); 
// from http://regexlib.com/REDetails.aspx?regexp_id=621 

, 나는 각 행을 포함하는 배열을 얻을 수있는 개행 문자에 문자열 분할을 수행이 안정적으로 작동 내가 찾은 유일한 하나입니다. 그러나 120MB 파일에서이 작업을 수행하면 System.OutOfMemoryException이됩니다. 컴퓨터에 4GB RAM이있을 때 메모리가 너무 빨리 소모되는 이유는 무엇입니까? 복잡한 CSV를 신속하게 구문 분석 할 수있는 더 좋은 방법이 있습니까?

답변

7

기본적으로 모든 할당 크기에 대해 OutOfMemoryException을 얻을 수 있습니다. 메모리 조각을 할당 할 때 요청 된 크기의 연속적인 메모리 조각을 정말로 요구하고 있습니다. 그 것을 존중할 수 없다면 OutOfMemoryException가 보일 것입니다.

또한 64 비트 Windows를 실행하지 않는 한 4GB RAM은 2GB 커널 공간과 2GB 사용자 공간으로 분할되므로 .NET 응용 프로그램은 기본값 당 2GB 이상을 액세스 할 수 없습니다.

.NET에서 문자열 연산을 수행 할 때 .NET 문자열이 변경되지 않기 때문에 일시적인 문자열이 많이 생성 될 수 있습니다. 따라서 메모리 사용이 상당히 크게 증가 할 수 있습니다.

+0

문자열은 컴퓨터 과학의 나쁜 자식입니다. 필요한 악,하지만 나는 여전히 누군가가 더 좋은 길을 찾아 낼 수 있기를 바란다. –

4

많은 연속 메모리가있는 단일 개체를 할당하지 못할 수도 있고 그렇게 할 수 있어야합니다. 스트리밍은이를 수행하는 일반적인 방법이지만 스트리밍은 속도가 느릴 수 있습니다 (일반적으로는별로 느리지는 않겠지 만).

큰 문제는 읽을 수 있습니다. 파일의 일부 (그러나 여전히 전체는 아님)를 StreamReader.ReadBlock()과 같은 기능으로 처리하고 차례로 각 부분을 처리합니다.

0

실제 메모리 사용량을 확인하려면 CLR profiler을 사용해보십시오. 시스템 RAM 이외의 메모리 제한이있을 수 있습니다. 예를 들어 이것이 IIS 응용 프로그램 인 경우 메모리는 응용 프로그램 풀로 제한됩니다.

이 프로필 정보를 사용하면 원래 시도한 CSV 파일의 스트리밍과 같이 확장 가능한 기술을 사용해야 할 수도 있습니다.

5

전체 파일을 문자열로 읽으려면 StringReader을 사용해야합니다.

StringReader reader = new StringReader(fileContents); 
string line; 
while ((line = reader.ReadLine()) != null) { 
    // Process line 
} 

이것은 내용이 메모리에 이미있는 것과 달리 파일에서 스트리밍하는 것과 대략 동일해야합니다.

편집

테스트 후 처리 line.Length와 길이 변수 증가 이루어져 1백40메가바이트 파일로 상기 시도했다. 컴퓨터에서 약 1.6 초가 걸렸습니다. 이 후 나는 다음을 시도했다 :

System.IO.StreamReader reader = new StreamReader("D:\\test.txt"); 
long length = 0; 
string line; 
while ((line = reader.ReadLine()) != null) 
    length += line.Length; 

결과는 약 1 초였다.

물론 네트워크 드라이브에서 읽는 중이거나 하드 드라이브가 다른 곳을 찾는데 충분히 오래 걸리는 경우 귀하의 마일리지가 다를 수 있습니다. FileStream을 사용하여 파일을 읽고 버퍼링하지 않는 경우에도 마찬가지입니다. StreamReader는 판독을 크게 향상시키는 버퍼링을 제공합니다.

+0

이것은 실제로 파일을 처음부터 문자열로 읽을 수 있다면 아주 좋은 대답입니다. 적어도 그 순간은 그가 할 수있는 것처럼 들립니다. 많은 기계가 즉시 120MB 파일을로드하는 데 실패하거나 (때로는 실패하고 다른 시간에 작동하지 않습니다.) – mquander

8

해야 할 때까지 자신의 파서를 굴리지 마십시오. 나는이 일에 행운을 했어 :

A Fast CSV Reader

아무것도 당신이 후드를보고 다른 사람이 그것을 어떻게하는지 볼 수 있다면.

+1

+1 큰 CSV 파일을 구문 분석하는 데 사용한대로 놀라지 않을 것입니다. – Wayne

+1

+1 나도. 제 경험으로 Sébastien Lorion의 CSV 리더는 효율적이고 유연하며 견고합니다. 그것은 시간이 없어 120MB 파일을 씹어 야합니다. – LukeH

0

힙이 아니라 스택에서 메모리가 부족합니다.

한 번에 120MB를 처리하는 대신 데이터를보다 관리하기 쉬운 "청크"로 처리하도록 앱을 다시 고려해 볼 수 있습니다.

+0

문자열은 스택이 아닌 힙에 할당됩니다. int/byte/double/등의 프리미티브 만 스택 imr에 할당됩니다. –

+0

@ 확실하지 않음 : 정확합니다. 그러나 프로그램 스택이 채워질 수있는 다양한 명확하지 않은 상황이 있습니다. 문제의 시스템이 충분한 물리적 메모리를 가지고 있다고 가정 할 때, 아마도이 경우 중 하나라고 생각합니다. =) – Garrett

+0

스택이 가득 차면 OutOfMemoryException이 아닌 StackOverflowException이됩니다. 후자는 항상 GC 힙의 메모리 부족을 나타내는 데 사용됩니다. –

1

다른 포스터에서 말한 것처럼 OutOfMemory는 요청 된 크기의 메모리 연속적 청크를 찾을 수 없기 때문입니다.

그러나 줄 단위로 구문 분석하는 것은 한 번에 모두 읽은 다음 처리하는 것보다 몇 배 빠르다고 말합니다.

while(! file.eof()) 
{ 
    string line = file.ReadLine(); 
    ProcessLine(line); 
} 

당신은 대신 스트림 쓰기 (에 의해 채워집니다 스트리밍을 사용한다)의 대체에서 호출하면 (의사 코드) 차단을하는 것은 예를 들어, 읽기의 순진한 접근 방식을 추구 한 경우에만 의미가 있습니다 쓰레드가 파일을 읽었으므로, 읽은 파일은 ProcessLine()이 무엇을 하든지간에 블록되지 않으며 그 반대도 마찬가지입니다. 그것은 한 번에 전체 파일을 읽은 다음 처리를 수행하는 성능에 부합해야합니다.

+0

멀티 스레드 방식의 코드 예제를 제공해 주시겠습니까? 나는 그것을 순진한 방식으로하고 있었고, 지금 이것이 왜 중요한 문제인지 이해합니다. –

+0

.Net에는 기본적으로 비동기 파일 읽기 및 쓰기 기능이 있기 때문에 BeginRead() 호출이 좋습니다. 다음 Google 결과에는 많은 예가 나와 있습니다. http://www.google.com/search?q=.net+asynchronous+file –

0

여기 대부분의 사람들이 동의합니다. 스트리밍을 사용해야합니다.

아무도 지금까지 말했는지 모르겠지만, exstention 방법을 봐야합니다.

그리고 나는, 확실히, 손을 아래로, .NET/CLR에서 최고의 CSV 분할 기술은 그 기술은 저를 생성 this one

알고 + 10 기가 바이트 XML 출력의 exstensive 입력 필터를 모두 포함하여 입력 CSV에서, 내가 본 것보다 더 빨리.

+0

Oh 맞아요, 스트리밍> RAM에 버퍼링. 4GIG를 사용하고 2GIG의 입력을로드하면 VM 하위 시스템의로드 시간과 쓰레기가 페이지를 다시 배치하고 페이지 테이블의 방대한 크기가 CPU 캐시 등을 막 먹이면됩니다. .. 작고 관리하기 쉬운 작업 공간을 유지하기 위해 캐시를 "뜨겁게"유지하고 모든 CPU 시간을 시스템 부하의 막대한 유출이 아닌 작업에 집중합니다. – RandomNickName42

0

청크를 버퍼로 읽고 작업해야합니다. 그런 다음 다른 청크를 읽으십시오.

효율적으로 처리 할 수있는 라이브러리가 많이 있습니다. 나는 CsvHelper라고 불리는 것을 유지합니다. 쉼표 나 줄 끝이 필드의 중간에있을 때와 같이 처리해야하는 많은 경우가 있습니다.