2009-07-29 3 views
5

우리 앱에서 성능 문제를 해결하는 동안 C의 stdio.h 함수 (적어도 우리의 공급 업체의 경우 C++의 fstream 클래스)가 스레드 안전성이 있다는 것을 알았습니다. 결과적으로 fgetc과 같은 간단한 작업을 할 때마다 RTL은 잠금을 획득하고 바이트를 읽고 잠금을 해제해야합니다.C/C++에서 스레드 안전하지 않은 파일 I/O

성능에 좋지 않습니다.

C 및 C++에서 비 threadsafe 파일 I/O를 얻는 가장 좋은 방법은 나 자신을 관리하고 더 나은 성능을 얻을 수 있도록하는 것입니다.

  • MSVC는 _fputc_nolock를 제공하고, GCC는 unlocked_stdioflockfile 제공하지만, 내 컴파일러 (코드기어 C++ 빌더)에서 유사한 기능을 찾을 수 없습니다.
  • 원시 Windows API를 사용할 수 있지만 이식성이 없으며 한 번에 한 문자 씩 I/O를 수행 할 때 잠금 해제 된 fgetc보다 느릴 것이라고 가정합니다.
  • Apache Portable Runtime과 같은 것으로 전환 할 수는 있지만 잠재적으로 많은 작업이 될 수 있습니다.

다른 사람들이 어떻게 접근합니까?

편집 : 몇 명의 사람들이 궁금해해서 게시하기 전에 이것을 테스트했습니다. fgetc은 버퍼에서 읽기를 만족할 수 있지만 여전히 잠금을 수행하기 때문에 시스템 호출을 수행하지 않으므로 잠금은 막대한 시간 (디스크에서 읽은 단일 블록의 데이터에 대해 획득하고 해제 할 수백 개의 잠금을 취함)). 한 번에 한 문자 씩 처리하는 것이 해결책이 될 수는 없지만 C++ 빌더의 fstream 클래스는 불행히도 fgetc를 사용합니다 (따라서 iostream 클래스를 사용하고 싶습니다.) 나는 많은 것을 가지고 있습니다. fgetc을 사용하는 레거시 코드와 친구가 레코드 스타일 파일에서 필드를 읽는 방법 (잠금 문제가 없다면 합리적 일 것입니다).

+0

C의 stdio.h 함수는 스레드 세이프가 아닙니다. 그것은 당신의 공급 업체이기도합니다. – MSalters

+0

내 공급 업체 만이 아닙니다. 예를 들어, POSIX에서는이를 요구합니다. –

답변

3

합리적인 성능의 현명한 경우에는 한 번에 숯을 사용하지 마십시오.

+0

스트림에 대한 모든 연산은 char IO를 발생시킵니다. 스트림 버퍼로 작업해야합니다. 아래에 게시 한 내용은 ... – ovanes

1

fgetc는 호출 할 때마다 거의 확실히 바이트를 읽지 않습니다 ('읽기'는 I/O를 수행하는 시스템 호출을 호출 함). 성능상의 병목 현상이있는 다른 곳을 찾아보십시오. 문제가 아니므로 안전하지 않은 기능을 사용하는 것이 해결책이 아닙니다. 사용자가 수행하는 모든 잠금 처리는 표준 루틴이 수행하는 처리보다 덜 효율적일 수 있습니다.

+1

한 번에 한 바이트 씩 읽지는 않지만 매회마다 잘 걸릴 수 있습니다. BTW는 POSIX에서 잠금을 취하는 것이 필수이며, getc_unlocked() (char IO 함수에 의해 char의 변종이 _unlocked되고 보호 될 수 있도록 잠금 함수가 있습니다)가 있습니다. – AProgrammer

1

가장 쉬운 방법은 메모리에있는 전체 파일을 읽은 다음 해당 버퍼에 사용자 자신의 fgetc와 같은 인터페이스를 제공하는 것입니다.

1

파일을 메모리 맵핑하는 것만 큼 없습니까? 메모리 매핑은 이식성이 뛰어납니다 (Windows Vista를 제외하고 지금 사용하려는 희망을 뛰어 넘어야 함, 바보). 아무튼, 파일을 메모리에 매핑하고 결과 메모리 위치에서 자체 잠금/잠금 해제를 수행합니까?

OS는 실제로 디스크에서 읽는 데 필요한 모든 잠금을 처리합니다.이 오버 헤드를 제거 할 수는 없습니다. 그러나 처리 오버 헤드는 다른 한편으로는 자신이 수행하는 것 이외의 외부 잠금에 의해 영향을받지 않습니다.

1

멀티 플랫폼 방식은 매우 간단합니다. 표준이 센트리를 사용해야한다고 명시한 기능이나 운영자는 피해야합니다. 보초는 모든 출력 문자에 대한 스트림 일관성을 보장하는 iostream 클래스의 내부 클래스이며 멀티 스레드 환경에서는 출력되는 각 문자에 대한 스트림 관련 뮤텍스를 잠급니다.

스레드 1 작성해야 : ABC
스레드 2 작성해야 : 데프

두 개의 스레드에서 문자열 출력은 다음 예와 같이 동시에 할 수 있기 때문에이 상태, 낮은 수준에서 경쟁 조건을 방지하지만 여전히 출력이 읽을 수 있습니다

출력은 abcdef 또는 defabc 대신 adebcf처럼 보일 수 있습니다. 센트리가 문자마다 잠금 및 잠금 해제되도록 구현 되었기 때문입니다.

표준은 istream 또는 ostream을 처리하는 모든 함수와 연산자에 대해이 표준을 정의합니다. 이를 방지하는 유일한 방법은 스트림 버퍼와 자체 잠금 (예 : 문자열 당)을 사용하는 것입니다.

나는 데이터를 파일로 출력하고 속도를 측정하는 app을 작성했다. 버퍼를 사용하지 않고 fstream을 직접 사용하여 플러시하는 함수를 여기에 추가하면 속도 차이가 나타납니다. 그것은 부스트를 사용하지만, 당신에게 문제가되지 않기를 바랍니다. 모든 streambuffers를 제거하고 차이가 있는지 여부를 확인하십시오. 저의 경우 성능 결점은 2-3 단계였습니다.

N. Myers의 following article은 C++ IOStreams에서 로케일과 보초가 어떻게 작동하는지 설명합니다. 그리고 ISO C++ 표준 문서를 살펴보아야합니다.이 함수는 센트리를 사용합니다.

행운을 빕니다,
Ovanes 고려해야 할

#include <vector> 
#include <fstream> 
#include <iterator> 
#include <algorithm> 
#include <iostream> 
#include <cassert> 
#include <cstdlib> 

#include <boost/progress.hpp> 
#include <boost/shared_ptr.hpp> 

double do_copy_via_streambuf() 
{ 
    const size_t len = 1024*2048; 
    const size_t factor = 5; 
    ::std::vector<char> data(len, 1); 

    std::vector<char> buffer(len*factor, 0); 

    ::std::ofstream 
    ofs("test.dat", ::std::ios_base::binary|::std::ios_base::out); 
    noskipws(ofs); 

    std::streambuf* rdbuf = ofs.rdbuf()->pubsetbuf(&buffer[0], buffer.size()); 

    ::std::ostreambuf_iterator<char> oi(rdbuf); 

    boost::progress_timer pt; 

    for(size_t i=1; i<=250; ++i) 
    { 
    ::std::copy(data.begin(), data.end(), oi); 
    if(0==i%factor) 
     rdbuf->pubsync(); 
    } 

    ofs.flush(); 
    double rate = 500/pt.elapsed(); 
    std::cout << rate << std::endl; 
    return rate; 
} 

void count_avarage(const char* op_name, double (*fct)()) 
{ 
    double av_rate=0; 
    const size_t repeat = 1; 
    std::cout << "doing " << op_name << std::endl; 
    for(size_t i=0; i<repeat; ++i) 
     av_rate+=fct(); 

    std::cout << "average rate for " << op_name << ": " << av_rate/repeat 
      << "\n\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n" 
      << std::endl; 
} 


int main() 
{ 
    count_avarage("copy via streambuf iterator", do_copy_via_streambuf); 
    return 0; 
} 
1

것은 사용자 정의 런타임을 구축하는 것입니다. 대부분의 컴파일러는 런타임 라이브러리에 소스를 제공합니다 (C++ Builder 패키지에 없으면 놀랍습니다).

이것은 많은 작업이 될 수 있지만 어쩌면 스레드 지원을 지역화하여 쉽게 이런 식으로 만들 수 있습니다. 예를 들어, 내가 사용하고있는 임베디드 시스템 컴파일러에서는 잠금 루틴을 추가하기 위해 후크를 문서화했습니다. 그러나 처음에는 상대적으로 쉬운 것으로 판명 되더라도 유지 관리의 어려움이 될 수 있습니다.

비슷한 다른 방법은 Dinkumware과 같은 사람에게 필요한 기능을 제공하는 타사 런타임을 사용하는 방법입니다.

관련 문제