2017-04-23 3 views
11

this link에 지정된 코딩 경쟁에서 stdin에 많은 데이터를 읽고, 몇 가지 계산을 수행하고 stdout에 많은 데이터를 표시해야하는 작업이 있습니다.고속 입출력 (표준 입출력)

필자는 벤치마킹에서 최대한 많은 것을 최적화하려고했지만 거의 시간이 걸린다.

입력 내용은 문자열 (1 <= len <= 100'000)이고 q 쌍의 정수 q는 1 <= q <= 100'000입니다.

나는 100 배 더 큰 데이터 세트에 내 코드를 벤치마킹 (LEN = 10M, Q = 10M) 이것은 결과입니다 : 내 자신의 형식화하는과 번호 분석 인라인을 구현함으로써

Activity   time  accumulated 

Read text:   0.004  0.004 
Read numbers:  0.146  0.150 
Parse numbers:  0.200  0.350 
Calc answers:  0.001  0.351 
Format output:  0.037  0.388 
Print output:  0.143  0.531 

내가 그럭저럭 printfscanf을 사용할 때 시간의 1/3까지 감소합니다.

그러나 내 솔루션을 경기 웹 페이지에 업로드 할 때 솔루션에 1.88 초가 걸렸습니다 (총 22 개 이상의 데이터 세트가 있다고 생각합니다). 높은 점수를 볼 때 0.05 초 만에 완성 된 몇 가지 구현 (C++)이 내 것보다 거의 40 배 빠릅니다! 어떻게 가능합니까?

2 스레드를 사용하여 속도를 높일 수 있다고 생각합니다. 그런 다음 stdout에서 계산하면서 stdout에 쓰기 시작할 수 있습니다. 그러나 큰 데이터 세트의 이론상 가장 좋은 경우에는 min(0.150, 0.143) 시간이 줄어 듭니다. 아직 최고 점수에 근접한 곳이 아닙니다.

아래 이미지에서 소비 된 시간의 통계를 볼 수 있습니다.

gcc -g -O2 -std=gnu99 -static my_file.c -lm 

과 같은 시간 초과 :

Statistics of the consumed time

이 프로그램은이 옵션을 사용하여 웹 사이트에 의해 컴파일됩니다

내 코드는 다음과 같습니다
time ./a.out <sample.in> sample.out 

:

#include <stdio.h> 
#include <stdlib.h> 
#include <string.h> 

#define MAX_LEN (100000 + 1) 
#define ROW_LEN (6 + 1) 
#define DOUBLE_ROW_LEN (2*ROW_LEN) 

int main(int argc, char *argv[]) 
{ 
    int ret = 1; 

    // Set custom buffers for stdin and out 
    char stdout_buf[16384]; 
    setvbuf(stdout, stdout_buf, _IOFBF, 16384); 
    char stdin_buf[16384]; 
    setvbuf(stdin, stdin_buf, _IOFBF, 16384); 

    // Read stdin to buffer 
    char *buf = malloc(MAX_LEN); 
    if (!buf) { 
     printf("Failed to allocate buffer"); 
     return 1; 
    } 
    if (!fgets(buf, MAX_LEN, stdin)) 
     goto EXIT_A; 

    // Get the num tests 
    int m ; 
    scanf("%d\n", &m); 

    char *num_buf = malloc(DOUBLE_ROW_LEN); 
    if (!num_buf) { 
     printf("Failed to allocate num_buffer"); 
     goto EXIT_A; 
    } 

    int *nn; 
    int *start = calloc(m, sizeof(int)); 
    int *stop = calloc(m, sizeof(int)); 
    int *staptr = start; 
    int *stpptr = stop; 
    char *cptr; 
    for(int i=0; i<m; i++) { 
     fgets(num_buf, DOUBLE_ROW_LEN, stdin); 
     nn = staptr++; 
     cptr = num_buf-1; 
     while(*(++cptr) > '\n') { 
      if (*cptr == ' ') 
       nn = stpptr++; 
      else 
       *nn = *nn*10 + *cptr-'0'; 
     } 
    } 


    // Count for each test 
    char *buf_end = strchr(buf, '\0'); 
    int len, shift; 
    char outbuf[ROW_LEN]; 
    char *ptr_l, *ptr_r, *out; 
    for(int i=0; i<m; i++) { 
     ptr_l = buf + start[i]; 
     ptr_r = buf + stop[i]; 
     while(ptr_r < buf_end && *ptr_l == *ptr_r) { 
      ++ptr_l; 
      ++ptr_r; 
     } 

     // Print length of same sequence 
     shift = len = (int)(ptr_l - (buf + start[i])); 
     out = outbuf; 
     do { 
      out++; 
      shift /= 10; 
     } while (shift); 
     *out = '\0'; 
     do { 
      *(--out) = ""[len%10]; 
      len /= 10; 
     } while(len); 
     puts(outbuf); 
    } 



    ret = 0; 

    free(start); 
    free(stop); 
EXIT_A: 
    free(buf); 
    return ret; 
} 
+0

왜 개별 int에 대해 메모리를 할당하고 있습니까? 너 무슨 시스템있어? Linux에서 stdio는 Windows에서 iostream보다 빠르며 빠릅니다. Windows에서는 iostream이 stdio를 능가합니다. stdio는 iostream (AFAIK)에 대한 그러한 요구 사항이없는 동안 POSIX가 stdio가 호출에 대해 재귀 적 잠금을 사용하도록 요구하므로 IO 함수의 잠금 해제 된 변형 (puts 대신에 puts_unlocked 등)을 사용하여 다소 빨리 수행 할 수 있습니다. – PSkocik

+0

루프를 통해 매번 출력을 수행하는 것처럼 보입니다. 속도를 위해 메모리를 교환하고 더 큰 버퍼를 할당 한 다음 전체 출력을 한꺼번에 인쇄한다면 어떨까요? 또는 실행 가능한 출력이 너무 많으면 버퍼링을 통해 출력을 실질적으로 통합 할 수 있습니다. 'puts'가 실제적으로 당신의 병목이라면 문제를 해결할 수 있습니다. 나는 당신이 그 시간에 도착하기 위해 어떻게 측정하고 있는지 확실하지 않습니다. 모든 작업이 "출력물 출력"측정에 포함되어 있습니까? –

+0

사소한 :'cptr = num_buf-1;'은 정의되지 않은 행동이다. – chux

답변

0

모든 버퍼를 연속적으로 할당해야합니다. 모든 버퍼 크기 (num_buff, start, stop)의 버퍼를 할당 한 다음 크기를 기준으로 해당 오프셋으로 포인트를 다시 정렬하십시오. 이렇게하면 캐시 누락 오류를 줄일 수 있습니다.

읽기 및 쓰기 작업은 많은 시간을 소비하는 것으로 보이므로 스레드 추가를 고려해야합니다. 하나의 스레드는 I \ O를 처리해야하고 다른 스레드는 계산을 처리해야합니다. (인쇄를위한 다른 스레드가 일을 빠르게 할 수 있는지 확인하는 것이 좋습니다.) 이 작업을 수행하는 동안 잠금을 사용하지 마십시오.

0

최적화 문제는 갖고있는 문제에 크게 의존하기 때문에이 질문에 대답하는 것은 까다 롭습니다. 하나의 아이디어는 읽으려고하는 파일의 내용을보고 자신이 선호하는 패턴이나 사물이 있는지 확인하는 것입니다. 작성한 코드는 파일에서 읽고, 항목을 실행 한 다음 파일에 쓰는 "일반적인"솔루션입니다. 그러나 파일을 매번 무작위로 생성하지 않고 내용이 항상 동일하면 그 파일에 대한 솔루션을 작성하지 않는 것이 좋습니다.

반면에 낮은 수준의 시스템 기능을 사용할 수 있습니다. 내 생각에 오는 한 가지는 mmap이며 파일을 직접 메모리에 매핑하고 scanffgets 대신 해당 메모리에 액세스 할 수 있습니다.

도움이 될만한 또 다른 사실은 두 개의 while 개의 루프가있는 solutin에있는 것입니다. 왜 하나만 사용해보십시오. 또 다른 일은 비동기 I/O 읽기를 수행하는 것이므로 루프에서 전체 파일을 읽은 다음 다른 루프에서 계산을 수행하는 대신 처음 부분을 읽은 다음 비동기 처리를 시작하고 계속 진행할 수 있습니다 독서. 이 link은 비동기 부분에 도움이 될 수 있습니다.

1

질문에 감사 드리며 직접 문제를 해결했습니다. 당신의 시간은 제 것보다 낫지 만, 아직도 몇 가지 stdio 기능을 사용하고 있습니다.

나는 단순히 0.05 초의 높은 점수가 진실이라고 생각하지 않는다. 나는 그것이 그 결과를 오류로 되 돌린 고도로 자동화 된 시스템의 제품이라고 생각한다.

어설 션을 방어하는 방법은 무엇입니까? 알고리즘의 복잡성은 없습니다. 문제는 O (n)입니다. "트릭"은 입력의 각 측면에 대해 특수 파서를 작성하는 것입니다 (디버그 모드에서만 수행되는 작업을 피하십시오). 22 개의 시행을위한 총 시간은 50 밀리 초이며, 각 시행 평균은 2.25ms입니까? 우리는 측정 가능성의 한계 근처에 있습니다.

자신이 해결 한 문제와 같은 공모전은 불행합니다. 퍼포먼스가 프로그램의 궁극적 인 측정이라는 순진한 생각을 강화합니다 (명확성을 위해 점수가 없습니다). 더 나쁜 것은 실제 성능에서 scanf와 같은 것들을 "성능을 위해"사용하는 것을 권장합니다. 프로그램이 정확하고 빠르게 실행되도록하는 것은 기본적으로 stdio를 피하거나 튜닝하는 것을 수반하지 않습니다. 복잡한 시스템에서 성능은 과 같은 것으로부터 I/O를 피하고 데이터를 한 번만 전달하고 복사본을 최소화합니다. DBMS를 효과적으로 사용하는 것은 종종 핵심적인 일이지만 프로그래밍과 관련된 문제는 결코 발생하지 않습니다.

숫자를 텍스트로 구문 분석하고 서식을 지정하는 데는 시간이 걸리고 드문 경우이지만 병목 현상이 발생할 수 있습니다. 그러나 해답은 거의 파서를 다시 작성하는 것이 아닙니다. 오히려, 대답은 텍스트를 편리한 2 진 형식으로 구문 분석하고이를 사용하는 것입니다. 요컨대 : 편집.

그렇다면 몇 가지 관찰이 도움이 될 수 있습니다.

이 문제는 동적 메모리가 필요하지 않으며 도움이되지 않습니다. 문제는 입력 배열이 최대 100,000 개의 요소 일 수 있으며 시행 횟수가 최대 100,000 개일 수 있다고 말합니다. 각 시험판은 공백으로 구분되고 개행으로 끝나는 최대 6 자리의 두 개의 정수 문자열입니다. 6 + 1 + 6 + 1 = 14. 총 입력은 최대 100,000 + 1 + 6 + 1 + 100,000 * 14 : 이하입니다. 16KB. 1GB의 메모리가 허용됩니다.

방금 ​​단일 16KB 버퍼를 할당하고 read (2)를 사용하여 한꺼번에 읽습니다. 그런 다음 그 입력을 한 번 통과 시켰습니다.

비동기 I/O 및 스레드를 사용하라는 제안이 있습니다. 문제 성명서는 CPU 시간을 측정 한 결과이므로 도움이되지 않습니다. 두 점 사이의 최단 거리는 직선입니다. 정적으로 할당 된 메모리로의 단일 읽기는 모션을 낭비하지 않습니다.

성능을 측정하는 방법 중 하나가 우스운 점은 gcc -g입니다.즉, 성능을 측정 한 코드에서 어설 션 (3)이 호출됩니다! 나는 나의 주장을 제거 할 때까지 테스트 22에서 4 초 미만으로 얻을 수 없었다.

요약하면, 당신은 꽤 잘했고, 당신이 당황한 우승자가 팬텀이라고 생각합니다. 코드가 약간 복잡해지며 동적 메모리를 없애고 표준을 조정할 수 있습니다. 나는 당신의 시간이 그것을 단순화함으로써 손질 될 수 있다고 생각한다. 성능이 중요하다는 점에서 내가주의를 집중시키는 부분입니다.