2012-11-20 2 views
3

줄 수만큼 파일을 읽고 분할/청크하는 방법은 무엇입니까?C + + 동시/비동기 처리를 위해 파일을 청크하는 방법?

두 개 이상의 버퍼 사이에서 줄이 분리되지 않도록 파일을 별도의 버퍼로 분할하고 싶습니다. 필자는 이러한 버퍼를 자신의 pthread에 전달하여 동시/비동기 처리를 수행 할 계획입니다.

나는 대답을 reading and writing in chunks on linux using c 아래에 읽었지만 정확히 두 개 이상의 버퍼로 분할되지 않도록하는 방법에 대한 질문에 대한 답변을 생각하지 않습니다.

+2

이런 종류의 구성표는 의도 한대로 작동하지 않습니다. 여러 개의 코어를 가진 프로세서가있어 여러 스레드의 생산성을 높이는 좋은 방법입니다. 하지만 여전히 디스크는 하나 뿐이며 스레드는 디스크를 읽는 것을 기다리고 있습니다. –

+0

@HansPassant : OP가 해당 작업이 CPU 바운드임을 알고있는 경우 해당되지 않을 수 있습니다. 그러나 그렇습니다, 당신은 틀림없이 정확하다. 'pbzip'과'xz' 둘 다 블록 레벨에서이 기술을 사용하지만 큰 효과가 있습니다. – Omnifarious

+0

사실 나는 그것에 대해 생각하지 않았습니다. 나는 내가 제기 한 질문을 사용하는 것을 포함하지 않는 나의 거시적 인 문제를 해결하는 더 깨끗한 방법을 생각 해왔다. 나는 아직도 대답이 무엇인지 궁금하다. – Ken

답변

1

청크 크기를 바이트 단위로 선택합니다. 그런 다음 파일의 적절한 위치를 찾고 줄 바꿈이 나올 때까지 작은 바이트 수를 읽습니다.

첫 번째 청크의 마지막 문자는 개행 문자입니다. 두 번째 청크의 첫 번째 문자는 개행 문자 다음의 문자입니다.

개행을 검색하려면 항상 pagesize() 경계를 탐색하고 한 번에 pagesize() 바이트를 읽으십시오. 이렇게하면 경계를 찾기 위해 디스크에서 필요한 최소량 만 가져 오는 경향이 있습니다. 한 번에 128 바이트를 읽으려고 시도 할 수도 있습니다. 그러나 그런 다음 더 많은 시스템 호출을 할 위험이 있습니다.

편지 빈도 계산을 위해 예제 프로그램을 작성했습니다. 물론 이것은 거의 확실히 IO 바인딩이므로 스레드로 분할하는 데는 무의미합니다. 또한 줄 바꿈이 아니기 때문에 개행 문자의 위치는 중요하지 않습니다. 그러나 이것은 단지 예일뿐입니다. 또한 합리적으로 완전한 C++ 11 구현을 사용하는 것에 크게 의존합니다. 또한 내가 pread를 사용하는 것을 알 수

// Find the offset of the next newline given a particular desired offset. 
off_t next_linestart(int fd, off_t start) 
{ 
    using ::std::size_t; 
    using ::ssize_t; 
    using ::pread; 

    const size_t bufsize = 4096; 
    char buf[bufsize]; 

    for (bool found = false; !found;) { 
     const ssize_t result = pread(fd, buf, bufsize, start); 
     if (result < 0) { 
     throw ::std::system_error(errno, ::std::system_category(), 
            "Read failure trying to find newline."); 
     } else if (result == 0) { 
     // End of file 
     found = true; 
     } else { 
     const char * const nl_loc = ::std::find(buf, buf + result, '\n'); 
     if (nl_loc != (buf + result)) { 
      start += ((nl_loc - buf) + 1); 
      found = true; 
     } else { 
      start += result; 
     } 
     } 
    } 
    return start; 
} 

: threaded_file_split.cpp on lisp.paste.org

그들은 키 기능

  • 이있다. 파일의 여러 부분에서 여러 스레드를 읽어야 할 때 이것이 절대적으로 필요합니다.

    파일 설명자는 스레드 간의 공유 리소스입니다. 한 스레드가 일반 함수를 사용하여 파일에서 읽을 때이 공유 리소스 인 파일 포인터에 대한 세부 정보가 변경됩니다. 파일 포인터는 파일에서 다음 읽기가 발생할 위치입니다.

    당신이이 lseekread 사이의 경쟁 조건을 소개하기 때문에 때마다이 도움이되지 않는 읽기 전에 간단히 lseek를 사용하여.

    pread 기능을 사용하면 파일 내의 특정 위치에서 많은 바이트를 읽을 수 있습니다. 또한 파일 포인터를 전혀 변경하지 않습니다. 파일 포인터를 변경하지 않는다는 것 외에도, 동일한 호출에서 lseekread을 결합하는 것과 같습니다.

+0

개행을 때리면 어떻게 알 수 있습니까? – Ken

+1

@Ken : 개행 문자로 읽은 버퍼를 검사합니다. 버퍼의 시작 부분 (lseek가 있던 곳)의 파일 오프셋과 개행 버퍼 내의 오프셋을 적어 둡니다. 둘을 추가하면 청크 크기에 가까운 줄 바꿈에 대한 파일 오프셋을 얻습니다. – Omnifarious

+0

@Ken - 저는 주로 제 기술을 사용하여 샘플 프로그램을 작성했습니다. 필자는 파일 액세스가 페이지 정렬되었는지 확인하려고 노력하지 않습니다. 가능한 한 빠른 작업을 원한다면 가능한 모든 파일 액세스가 페이지 정렬되었는지와 버퍼가 페이지 정렬되었는지 확인해야합니다. 이를 통해 OS는 복사가 아닌 프로세스의 주소 공간에 버퍼를 매핑함으로써 읽기를 최적화 할 수 있습니다. – Omnifarious

2

파일은 어떻게 인코딩됩니까? 각 바이트가 문자를 나타내는 경우 다음을 수행합니다.

  1. 메모리는 mmap()을 사용하여 파일을 매핑합니다.
  2. 적절한 청크 크기에 따라 작업을 계산하여 작업의 대략적인 시작 및 종료를 알립니다.
  3. 각 작업이 다음 '\n'을 찾아서 실제 시작과 끝을 찾았습니까?
  4. 각 청크를 동시에 처리합니다.
  5. 첫 번째 청크는 시작이 근사값이 아니지만 정확하기 때문에 특수 처리가 필요합니다.
0

버퍼의 클래스를 정의하십시오. 각 페이지 크기의 몇 배인 큰 버퍼 공간과 전달/종료 인덱스, 전달 된 스트림에서 버퍼를 읽는 메소드 및 다른 * 버퍼 인스턴스를 매개 변수로 사용하는 'lineParse'메소드를 제공하십시오.

일부 버퍼를 만들고 생성자 - 소비자 풀 대기열에 저장합니다. 파일을 열고 풀에서 버퍼를 가져오고 처음부터 끝까지 버퍼 공간을 읽습니다 (오류/EOF에 대한 부울 반환). 풀에서 다른 * 버퍼를 가져 와서 이전 버퍼의 lineparse()에 전달합니다. 거기에서, 데이터의 끝에서 뒤로 검색하여 newLine을 찾습니다. 발견되면 마지막 인덱스를 다시로드하고 마지막 라인의 조각을 memcpy (새로운 행이있을 경우 때때로 운이 좋을 수도 있습니다 :), 전달 된 새 버퍼에 넣고 시작 인덱스를 설정합니다. 첫 번째 버퍼는 이제 전체 라인을 가지며 라인을 처리 할 스레드/s에 큐에 넣을 수 있습니다. 두 번째 버퍼는 첫 번째부터 복사 된 라인 조각을 가지고 있으며 더 많은 데이터를 디스크의 시작 인덱스에서 버퍼 공간으로 읽을 수 있습니다.

줄 처리 스레드는 'used'버퍼를 다시 풀로 재활용 할 수 있습니다.

EOF까지 계속 진행하십시오 (또는 오류 :).

가능한 경우 버퍼 처리를 수행하는 버퍼 클래스에 메서드를 추가하십시오.

큰 버퍼 클래스를 사용하고 끝에서 파싱하는 것은 작은 비트를 계속해서 읽는 것보다 효율적입니다. 처음부터 줄 바꿈을 찾습니다. 스레드 간 통신이 느리고 전달할 수있는 버퍼가 클수록 좋습니다.

버퍼 풀을 사용하면 지속적으로 새로운/삭제를 제거하고 흐름 제어를 제공합니다. 디스크 읽기 스레드가 처리보다 빠르면 풀이 비워지고 일부 사용 된 버퍼가 재활용 될 때까지 디스크 읽기 스레드가 블록을 차단합니다 . 이것은 메모리 폭주를 방지합니다.

처리 스레드를 두 개 이상 사용하는 경우 버퍼가 순서없이 처리 될 수 있습니다. 이는 문제가 될 수도 있고 아닐 수도 있습니다.

디스크 읽기 대기 시간과 병행하여 처리되는 라인의 이점이 스레드 간 통신의 오버 헤드보다 큰지 확인하여 스레드간에 작은 버퍼를 통신 할 경우에만 이점을 얻을 수 있습니다. 생산적인.

전반적으로 빠르지 만 대기 시간이 긴 네트워크 디스크의 경우 가장 빠른 속도 향상을 경험할 수 있습니다.

관련 문제