"예, PHP는 C#보다 빠릅니다"에 대한 응답

14151 단어 phpperformancecsharp
최근에 Yes, PHP Is Faster Than C#이라는 제목의 블로그 게시물이 있어 상당한 대화를 촉발했습니다. 나는 게시물에 언급된 테스트를 실행하기로 결정했고 공유할 가치가 있다고 생각하는 흥미로운 결과를 찾았습니다.
여기에 사용된 벤치마크는 파일 시스템에서 파일을 4KiB 청크로 읽고 파일의 값1으로 바이트 수를 계산합니다. 먼저, 특히 디스크에서 파일을 읽는 것이 관련되어 있기 때문에 이 "벤치마크"가 매우 의미가 있다고 생각하지 않는다고 말하면서 시작하겠습니다. 파일 시스템 성능(캐시, 디스크 드라이브의 상태, 그 당시 커널이 얼마나 바쁜지)에 영향을 줄 수 있는 많은 것들이 있지만 그 중 어느 것도 테스트 자체에서 주소가 아닙니다.
그럼에도 불구하고 결과는 우리가 이야기할 수 있는 몇 가지 흥미로운 성능 특성을 나타냅니다.
테스트를 위한 소스 코드는 여기에서 찾을 수 있습니다: https://github.com/dhhoang/csharp-php-file-read

작은 파일



이렇게 테스트 파일을 생성했습니다.

# for this test, we will use file_size of 4 MiB as specified in the original post
base64 /dev/urandom | head -c [file_size] > test.txt


PHP(8.0) 프로그램의 코드는 다음과 같습니다.

function test()
{
    $file = fopen("/path/to/test.txt", 'r');
    $counter = 0;
    $timer = microtime(true);
    while ( ! feof($file)) {
        $buffer = fgets($file, 4096);
        $counter += substr_count($buffer, '1');
    }
    $timer = microtime(true) - $timer;
    fclose($file);
    printf("counted %s 1s in %s milliseconds\n", number_format($counter), number_format($timer * 1000, 4));
}
test();


그리고 C#의 경우:

private static void Test()
{
    using var file = File.OpenRead("/path/to/test.txt");
    var counter = 0;
    var buffer = new byte[4096];
    var numRead = 0;
    var sw = Stopwatch.StartNew();
    while ((numRead = file.Read(buffer, 0, buffer.Length)) != 0)
    {
        counter += buffer.Take(numRead).Count((x) => x == '1');
    }
    sw.Stop();
    Console.WriteLine($"Counted {counter} 1s in {sw.ElapsedMilliseconds} milliseconds");
}
Test();


t3-xlarge EC2 인스턴스에서 실행한 결과는 다음과 같습니다(참고: 코드는 10번 실행되고 런타임은 콜드 파일 캐시로 인한 이상을 제거한 후 평균)

Test-C#      53.2ms
Test-PHP     11.1ms


따라서 PHP 코드는 C# 코드보다 약 5배 빠릅니다!!! PHP가 실제로 C#보다 빠른 것 같습니까?
뭔가 확실히 여기에서 벗어났습니다. 파일을 읽을 때 .NET이 그렇게 느립니까? 아마 그렇지 않을 것입니다. 두 프로그램에서 "카운팅"부분을 제거한 간단한 테스트를 수행했는데 성능이 매우 유사해졌습니다. 블로그 작성자는 테스트에 "사용자 영역 코드가 거의"없고 주로 파일 읽기 성능을 테스트한다고 주장했습니다. 나는 이것이 잘못된 것으로 나타났습니다.
이제 두 프로그램을 자세히 보면 1 바이트를 세는 부분을 제외하고는 매우 유사합니다. PHP는 매우 최적화된 내장 함수substr_count를 사용하는 반면 C# 코드는 LINQ를 사용합니다. LINQ는 C#에서 컬렉션을 사용하는 매우 편리한 방법이지만 속도도 상당히 느립니다. 구식 방식으로 바이트 수를 계산하려고 하면 어떻게 될까요?

private static void Test_FileStream_NoLinq()
{
...
    while ((numRead = file.Read(buffer, 0, buffer.Length)) != 0)
    {
        for (var c = 0; c < numRead; c++)
        {
            if (buffer[c] == '1')
            {
                counter++;
            }
        }
    }
...
}


이제 결과는 다음과 같습니다(Test-C#-NoLinq 참조).

Test-C#             53.2ms
Test-PHP            11.1ms
Test-C#-NoLinq      6.5ms


따라서 이 시점에서 C#은 이미 이전보다 훨씬 빠르게 수행하고 있으며 PHP 프로그램보다 약 두 배 빠릅니다. 이것은 바이트 카운팅 프로세스가 총 실행 시간에 크게 기여함을 보여줍니다.
그래서 다음 질문은 우리가 더 잘할 수 있습니까? 바이트 버퍼로 작업할 때 개별 바이트를 반복하는 것은 매우 순진한 구현입니다. 보다 최적화된 방법은 SIMD와 같은 벡터화 기술을 활용하는 것입니다. 사실 substr_count 함수가 벡터화를 사용하지 않는다면 매우 놀랄 것입니다. 이를 테스트하기 위해 substr_count 를 사용하는 대신 문자열을 반복하는 또 다른 PHP 테스트 함수를 만들었습니다. 이는 C# Test_FileStream_NoLinq 함수와 비슷합니다.

function test_manual_count()
{
    ...
    while ( ! feof($file)) {
        $buffer = fgets($file, 4096);
        $length = strlen($buffer);
        for ($i = 0; $i < $length; $i++) {
            if($buffer[$i]=='1'){
                $counter += 1;
            }
        }
    }
    ...
}


결과(Test-PHP-Manual-Count 참조):

Test-C#-NoLinq          6.5ms
Test-PHP                11.1ms
Test-PHP-Manual-Count   135ms


그것은 고통스럽게 느리기 때문에 문자열에서 발생 횟수를 계산해야 할 때 항상 substr_count를 사용하는 것이 좋습니다. 불행히도 C#은 동일한 기능을 가진 내장 메서드를 제공하지 않지만 많은 primitives for implementing vectorization 을 제공합니다. StackOverflow : VectorExtensions.OccurrencesOf(ReadonlySpan<byte>, byte) 에서 SIMD에 해당하는 기능의 구현을 찾았습니다. 이를 통해 카운터를 다시 작성할 수 있습니다.

private static void Test_FileStream_Vectorized()
{
...
    while ((numRead = file.Read(buffer, 0, buffer.Length)) != 0)
    {
        counter += buffer.AsSpan().Slice(0, numRead).OccurrencesOf((byte)'1');
    }
...
}


결과(Test-C#-Vectorization 참조):

Test-C#-NoLinq          6.5ms
Test-C#-Vectorization   1.0ms


이는 수동 루프보다 6배 빠르고 PHP보다 약 10배 빠릅니다 😊.

대용량 파일



이 테스트에서는 3.2GBUbuntu ISO image를 사용하고 있습니다. 결과는 다음과 같습니다.

Test-PHP                3228.4ms
Test-PHP-Manual-Count   103966.7ms
Test-C#-NoLinq          5175.3ms
Test-C#-Vectorization   1104.7ms


여기에서 벡터화를 사용하면 두 언어 모두에서 작업이 훨씬 더 빨라지는 방법을 명확하게 볼 수 있습니다.

좋은 웹페이지 즐겨찾기