찾다
백엔드 개발Golang외부 병합 문제 - Gophers를 위한 완벽한 가이드

외부 정렬 문제는 컴퓨터 과학 강좌에서 잘 알려진 주제이며 교육 도구로 자주 사용됩니다. 그러나 필요한 최적화 작업은 고사하고 특정 기술 시나리오에 대한 코드에서 이 문제에 대한 솔루션을 실제로 구현한 사람을 만나는 경우는 거의 없습니다. 해커톤 중 이러한 문제에 직면하면서 이 글을 쓰게 되었습니다.

해커톤 과제는 다음과 같습니다.

IPv4 주소가 포함된 간단한 텍스트 파일이 있습니다. 한 줄은 한 줄씩 하나의 주소입니다.

145.67.23.4
8.34.5.23
89.54.3.124
89.54.3.124
3.45.71.5
... 

파일 크기는 무제한이며 수십, 수백 기가바이트를 차지할 수 있습니다.

가능한 적은 메모리와 시간을 사용하여 이 파일의 고유 주소 수를 계산해야 합니다. 이 문제를 해결하기 위한 "순진한" 알고리즘이 있습니다(한 줄씩 읽고, HashSet에 줄을 넣습니다). 구현이 이 순진한 알고리즘보다 더 복잡하고 빠르면 더 좋습니다.

파싱을 위해 80억 줄의 120GB 파일이 제출되었습니다.

프로그램 실행 속도에 대한 특별한 요구 사항은 없었습니다. 그러나 온라인에서 해당 주제에 대해 사용 가능한 정보를 신속하게 검토한 후 표준 하드웨어(예: 가정용 PC)에 허용되는 실행 시간은 약 1시간 이하라는 결론을 내렸습니다.

분명한 이유로 시스템에 사용 가능한 메모리가 최소 128GB가 아니면 파일 전체를 읽고 처리할 수 없습니다. 그런데 청크로 작업하고 병합하는 것은 불가피한가요?

외부 병합을 구현하는 것이 불편하다면 먼저 최적과는 거리가 멀지만 허용되는 대체 솔루션에 익숙해지는 것이 좋습니다.

아이디어

  • 2^32비트 비트맵을 만듭니다. uint64에는 64비트가 포함되어 있으므로 이는 uint64 배열입니다.

  • 각 IP에 대해:

  1. 문자열 주소를 4개의 옥텟(A.B.C.D)으로 구문 분석합니다.
  2. 숫자로 변환 ipNum = (A
  3. 비트맵에서 해당 비트를 설정합니다.
  • 1. 모든 주소를 읽은 후 비트맵을 실행하여 설정된 비트 수를 계산합니다.

장점:
매우 빠른 고유성 감지: 비트 O(1) 설정, 확인할 필요 없이 그냥 설정하기만 하면 됩니다.

해싱, 정렬 등에 대한 오버헤드가 없습니다.
단점:
막대한 메모리 소비(오버헤드를 고려하지 않고 전체 IPv4 공간에 512MB).

파일이 크지만 전체 IPv4 공간보다 작은 경우 시간 측면에서는 여전히 유리할 수 있지만 메모리 측면에서는 항상 합리적인 것은 아닙니다.

package main

import (
    "bufio"
    "fmt"
    "os"
    "strconv"
    "strings"
    "math/bits"
)

//  Parse IP address "A.B.C.D"  => uint32 number
func ipToUint32(ipStr string) (uint32, error) {
    parts := strings.Split(ipStr, ".")
    if len(parts) != 4 {
        return 0, fmt.Errorf("invalid IP format")
    }

    var ipNum uint32
    for i := 0; i  255 {
            return 0, fmt.Errorf("invalid IP octet: %v", parts[i])
        }
        ipNum = (ipNum 



<p>이 접근 방식은 간단하고 안정적이므로 대안이 없을 때 실행 가능한 옵션입니다. 그러나 프로덕션 환경에서는 특히 최적의 성능을 달성하려는 경우 보다 효율적인 솔루션을 개발하는 것이 필수적입니다.</p><p>따라서 우리의 접근 방식에는 청킹, 내부 병합 정렬, 중복 제거가 포함됩니다.</p>

<h2>
  
  
  외부 정렬의 병렬화 원리
</h2>

<ol>
<li><strong>청크 읽기 및 변환:</strong></li>
</ol>

<p>파일은 수백 메가바이트 또는 몇 기가바이트와 같이 비교적 작은 부분(청크)으로 분할됩니다. 각 청크에 대해:</p>

  • 청크를 읽고 IP 주소를 숫자로 구문 분석하여 메모리의 임시 배열에 저장하는 고루틴(또는 고루틴 풀)이 시작됩니다.

  • 그런 다음 이 배열이 정렬되고(예: 표준 sort.Slice를 사용하여) 중복 항목을 제거한 후 결과가 임시 파일에 기록됩니다.

각 부분은 독립적으로 처리될 수 있으므로 CPU 코어가 여러 개이고 디스크 대역폭이 충분하다면 이러한 핸들러 여러 개를 병렬로 실행할 수 있습니다. 이를 통해 리소스를 최대한 효율적으로 사용할 수 있습니다.

  1. 정렬된 청크 병합(병합 단계):

모든 청크가 정렬되어 임시 파일에 기록되면 이러한 정렬된 목록을 단일 정렬 스트림으로 병합하여 중복 항목을 제거해야 합니다.

  • 외부 정렬 과정과 유사하게 여러 개의 임시 파일을 그룹으로 나누어 병렬로 병합하고 점차적으로 파일 수를 줄여 병합을 병렬화할 수 있습니다.

  • 이렇게 하면 정렬되고 중복이 제거된 하나의 큰 출력 스트림이 남고, 여기에서 총 고유 IP 수를 계산할 수 있습니다.

병렬화의 장점:

  • 다중 CPU 코어 사용:
    매우 큰 배열을 단일 스레드로 정렬하면 속도가 느려질 수 있지만, 멀티 코어 프로세서가 있으면 여러 청크를 병렬로 정렬하여 프로세스 속도를 몇 배로 높일 수 있습니다.

  • 로드 밸런싱:

청크 크기를 현명하게 선택하면 각 청크를 거의 동일한 시간 내에 처리할 수 있습니다. 일부 청크가 더 크거나 작거나 더 복잡한 경우 여러 고루틴에 걸쳐 해당 처리를 동적으로 배포할 수 있습니다.

  • IO 최적화:

병렬화를 사용하면 한 청크를 읽는 동안 다른 청크를 정렬하거나 쓸 수 있어 유휴 시간이 줄어듭니다.

결론

외부 정렬은 자연스럽게 파일 청크를 통한 병렬화에 적합합니다. 이 접근 방식을 사용하면 멀티 코어 프로세서를 효율적으로 사용할 수 있고 IO 병목 현상이 최소화되므로 단일 스레드 접근 방식에 비해 훨씬 더 빠른 정렬 및 중복 제거가 가능합니다. 워크로드를 효과적으로 분산함으로써 대용량 데이터세트를 처리할 때도 높은 성능을 발휘할 수 있습니다.

중요 고려 사항:

파일을 한 줄씩 읽는 동안 총 줄 수도 계산할 수 있습니다. 프로세스가 진행되는 동안 우리는 청크 분할과 병합의 두 단계로 중복 제거를 수행합니다. 결과적으로 최종 출력 파일의 행 수를 계산할 필요가 없습니다. 대신 총 고유 줄 수는 다음과 같이 계산할 수 있습니다.

finalCount := totalLines - (DeletedInChunks DeletedInMerge)

이 접근 방식은 중복 작업을 방지하고 각 중복 제거 단계에서 삭제 항목을 추적하여 계산을 더욱 효율적으로 만듭니다. 이를 통해 많은 시간을 절약할 수 있습니다.

한 가지 더:

방대한 양의 데이터에서는 작은 성능 향상도 중요하므로 직접 작성한 문자열 가속 아날로그를 사용하는 것이 좋습니다.Slice()

145.67.23.4
8.34.5.23
89.54.3.124
89.54.3.124
3.45.71.5
... 

또한 병렬 처리를 관리하기 위해 작업자 템플릿이 채택되었으며 스레드 수를 구성할 수 있습니다. 기본적으로 스레드 수는 runtime.NumCPU()로 설정되어 프로그램이 사용 가능한 모든 CPU 코어를 효율적으로 활용할 수 있습니다. 이 접근 방식은 최적의 리소스 사용을 보장하는 동시에 환경의 특정 요구 사항이나 제한 사항에 따라 스레드 수를 조정할 수 있는 유연성을 제공합니다.

중요 사항: 멀티스레딩을 사용할 때 경합 상태를 방지하고 프로그램의 정확성을 보장하기 위해 공유 데이터를 보호하는 것이 중요합니다. 이는 구현의 특정 요구 사항에 따라 뮤텍스, 채널(Go에서) 또는 기타 동시성이 안전한 기술과 같은 동기화 메커니즘을 사용하여 달성할 수 있습니다.

지금까지의 요약

이러한 아이디어를 구현한 결과, M.2 SSD와 결합된 Ryzen 7700 프로세서에서 실행될 때 약 40분 만에 작업을 완료하는 코드가 탄생했습니다.

압축을 고려합니다.

데이터의 양과 그에 따른 중요한 디스크 작업의 존재 여부를 기준으로 다음 고려 사항은 압축 사용이었습니다. 압축을 위해 Brotli 알고리즘이 선택되었습니다. 높은 압축률과 효율적인 압축 해제 덕분에 디스크 IO 오버헤드를 줄이는 동시에 중간 저장 및 처리 중에 우수한 성능을 유지하는 데 적합한 선택입니다.

다음은 Brotli를 사용한 청킹의 예입니다.

package main

import (
    "bufio"
    "fmt"
    "os"
    "strconv"
    "strings"
    "math/bits"
)

//  Parse IP address "A.B.C.D"  => uint32 number
func ipToUint32(ipStr string) (uint32, error) {
    parts := strings.Split(ipStr, ".")
    if len(parts) != 4 {
        return 0, fmt.Errorf("invalid IP format")
    }

    var ipNum uint32
    for i := 0; i  255 {
            return 0, fmt.Errorf("invalid IP octet: %v", parts[i])
        }
        ipNum = (ipNum 



<h2>
  
  
  압축 사용 결과
</h2>

<p>압축의 효율성은 논쟁의 여지가 있으며 솔루션이 사용되는 조건에 따라 크게 달라집니다. 압축률이 높으면 디스크 공간 사용량이 줄어들지만 비례적으로 전체 실행 시간이 늘어납니다. 느린 HDD에서는 디스크 I/O가 병목 현상을 일으키므로 압축을 통해 속도가 크게 향상될 수 있습니다. 반대로 빠른 SSD에서는 압축으로 인해 실행 시간이 느려질 수 있습니다.</p><p>M.2 SSD가 탑재된 시스템에서 실시한 테스트에서는 압축 시 성능 향상이 나타나지 않았습니다. 그 결과 결국 포기하기로 결정했습니다. 그러나 코드가 복잡해지고 잠재적으로 가독성이 떨어질 위험이 있는 경우 구성 가능한 플래그로 제어되는 압축을 선택적 기능으로 구현할 수 있습니다.</p>

<h2>
  
  
  다음에해야 할 일
</h2>

<p>추가적인 최적화를 추구하면서 우리는 솔루션의 바이너리 변환에 관심을 돌렸습니다. 텍스트 기반 IP 주소가 숫자 해시로 변환되면 이후의 모든 작업을 바이너리 형식으로 수행할 수 있습니다.<br>
</p>

<pre class="brush:php;toolbar:false">145.67.23.4
8.34.5.23
89.54.3.124
89.54.3.124
3.45.71.5
... 

바이너리 형식의 장점

  • 컴팩트함:

각 숫자는 고정된 크기를 차지합니다(예: uint32 = 4바이트).
1백만 개의 IP 주소의 경우 파일 크기는 ~4MB에 불과합니다.

  • 빠른 처리:

문자열을 구문 분석할 필요가 없으므로 읽기 및 쓰기 작업 속도가 빨라집니다.

  • 교차 플랫폼 호환성:

일관적인 바이트 순서(LittleEndian 또는 BigEndian)를 사용하면 다양한 플랫폼에서 파일을 읽을 수 있습니다.

결론
데이터를 이진 형식으로 저장하는 것이 숫자를 쓰고 읽는 데 더 효율적인 방법입니다. 완전한 최적화를 위해 데이터 쓰기 및 읽기 프로세스를 모두 바이너리 형식으로 변환합니다. 쓰기에는 Binary.Write를 사용하고 읽기에는 Binary.Read를 사용하세요.

이진 형식으로 작업하는 processChunk 함수의 모습은 다음과 같습니다.

package main

import (
    "bufio"
    "fmt"
    "os"
    "strconv"
    "strings"
    "math/bits"
)

//  Parse IP address "A.B.C.D"  => uint32 number
func ipToUint32(ipStr string) (uint32, error) {
    parts := strings.Split(ipStr, ".")
    if len(parts) != 4 {
        return 0, fmt.Errorf("invalid IP format")
    }

    var ipNum uint32
    for i := 0; i  255 {
            return 0, fmt.Errorf("invalid IP octet: %v", parts[i])
        }
        ipNum = (ipNum 



<h2>
  
  
  뭐야?! 많이 느려졌습니다!!
</h2>

<p>바이너리 형식에서는 작동 속도가 느려졌습니다. 1억 줄(IP 주소)이 포함된 파일은 바이너리 형식으로 처리되는 데 4.5분, 텍스트에서는 25초가 걸립니다. 청크 크기와 작업자 수가 동일합니다. 왜요?</p>

<p><strong>바이너리 형식으로 작업하면 텍스트 형식보다 속도가 느려질 수 있습니다</strong><br>
바이너리 형식을 사용하면 바이너리.읽기 및 바이너리.쓰기 작동 방식의 세부 사항과 구현 시 잠재적인 비효율성으로 인해 텍스트 형식보다 속도가 느려질 수 있습니다. 이런 일이 발생할 수 있는 주요 이유는 다음과 같습니다.</p>

<p><strong>I/O 작업</strong></p>

  • 텍스트 형식:

행 읽기에 최적화된 bufio.Scanner를 사용하여 더 큰 데이터 블록으로 작업합니다.
전체 줄을 읽고 구문 분석하므로 소규모 변환 작업에 더 효율적일 수 있습니다.

  • 바이너리 형식:

binary.Read는 한 번에 4바이트를 읽으므로 소규모 I/O 작업이 더 자주 발생합니다.
바이너리에 대한 빈번한 호출. 사용자와 시스템 공간 간 전환으로 인해 오버헤드가 증가합니다.

해결책: 여러 숫자를 한 번에 읽으려면 버퍼를 사용하세요.

func fastSplit(s string) []string {
    n := 1
    c := DelimiterByte

    for i := 0; i 



<p><strong>버퍼링이 성능을 향상시키는 이유는 무엇입니까?</strong></p>
  • I/O 작업 감소:
    각 숫자를 디스크에 직접 쓰는 대신 데이터가 버퍼에 축적되어 더 큰 블록에 기록됩니다.

  • 오버헤드 감소:

각 디스크 쓰기 작업은 프로세스와 운영 체제 간의 컨텍스트 전환으로 인해 오버헤드를 발생시킵니다. 버퍼링을 사용하면 이러한 통화 횟수가 줄어듭니다.

또한 바이너리 다상 병합을 위한 코드도 제시합니다:

145.67.23.4
8.34.5.23
89.54.3.124
89.54.3.124
3.45.71.5
... 

결과는 환상적입니다. 80억 줄이 있는 110Gb 파일의 경우 14분!

Image description

훌륭한 결과네요! 80억 줄이 포함된 110GB 파일을 14분 만에 처리하는 것은 정말 인상적입니다. 다음의 힘을 보여줍니다:

  • 버퍼 I/O:

라인 단위나 값 단위가 아닌 메모리에서 대량의 데이터를 처리함으로써 종종 병목 현상이 발생하는 I/O 작업 수를 대폭 줄일 수 있습니다.

  • 최적화된 바이너리 처리:

바이너리 읽기 및 쓰기로 전환하면 구문 분석 오버헤드가 최소화되고 중간 데이터 크기가 줄어들며 메모리 효율성이 향상됩니다.

  • 효율적인 중복 제거:

중복 제거 및 정렬을 위해 메모리 효율적인 알고리즘을 사용하면 CPU 주기를 효과적으로 활용할 수 있습니다.

  • 병렬성:

고루틴과 채널을 활용하여 작업자 간 워크로드를 병렬화하면 CPU와 디스크 사용률의 균형이 유지됩니다.

결론

마지막으로 최종 솔루션의 전체 코드는 다음과 같습니다. 자유롭게 사용하고 필요에 맞게 조정하세요!

Gophers를 위한 외부 병합 솔루션

행운을 빌어요!

위 내용은 외부 병합 문제 - Gophers를 위한 완벽한 가이드의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
PPROF 도구를 사용하여 GO 성능을 분석하는 방법은 무엇입니까?PPROF 도구를 사용하여 GO 성능을 분석하는 방법은 무엇입니까?Mar 21, 2025 pm 06:37 PM

이 기사는 프로파일 링 활성화, 데이터 수집 및 CPU 및 메모리 문제와 같은 일반적인 병목 현상을 식별하는 등 GO 성능 분석을 위해 PPROF 도구를 사용하는 방법을 설명합니다.

Debian Openssl의 취약점은 무엇입니까?Debian Openssl의 취약점은 무엇입니까?Apr 02, 2025 am 07:30 AM

보안 통신에 널리 사용되는 오픈 소스 라이브러리로서 OpenSSL은 암호화 알고리즘, 키 및 인증서 관리 기능을 제공합니다. 그러나 역사적 버전에는 알려진 보안 취약점이 있으며 그 중 일부는 매우 유해합니다. 이 기사는 데비안 시스템의 OpenSSL에 대한 일반적인 취약점 및 응답 측정에 중점을 둘 것입니다. DebianopensSL 알려진 취약점 : OpenSSL은 다음과 같은 몇 가지 심각한 취약점을 경험했습니다. 심장 출혈 ​​취약성 (CVE-2014-0160) :이 취약점은 OpenSSL 1.0.1 ~ 1.0.1F 및 1.0.2 ~ 1.0.2 베타 버전에 영향을 미칩니다. 공격자는이 취약점을 사용하여 암호화 키 등을 포함하여 서버에서 무단 읽기 민감한 정보를 사용할 수 있습니다.

GO에서 단위 테스트를 어떻게 작성합니까?GO에서 단위 테스트를 어떻게 작성합니까?Mar 21, 2025 pm 06:34 PM

이 기사는 GO에서 단위 테스트 작성, 모범 사례, 조롱 기술 및 효율적인 테스트 관리를위한 도구를 다루는 것에 대해 논의합니다.

이동 중에 테스트를 위해 모의 개체와 스터브를 작성하려면 어떻게합니까?이동 중에 테스트를 위해 모의 개체와 스터브를 작성하려면 어떻게합니까?Mar 10, 2025 pm 05:38 PM

이 기사는 단위 테스트를 위해 이동 중에 모의와 스터브를 만드는 것을 보여줍니다. 인터페이스 사용을 강조하고 모의 구현의 예를 제공하며 모의 집중 유지 및 어설 션 라이브러리 사용과 같은 모범 사례에 대해 설명합니다. 기사

GO에서 제네릭에 대한 사용자 정의 유형 제약 조건을 어떻게 정의 할 수 있습니까?GO에서 제네릭에 대한 사용자 정의 유형 제약 조건을 어떻게 정의 할 수 있습니까?Mar 10, 2025 pm 03:20 PM

이 기사에서는 GO의 제네릭에 대한 사용자 정의 유형 제약 조건을 살펴 봅니다. 인터페이스가 일반 함수에 대한 최소 유형 ​​요구 사항을 정의하여 유형 안전 및 코드 재사성을 향상시키는 방법에 대해 자세히 설명합니다. 이 기사는 또한 한계와 모범 사례에 대해 설명합니다

Go의 반사 패키지의 목적을 설명하십시오. 언제 반사를 사용 하시겠습니까? 성능의 영향은 무엇입니까?Go의 반사 패키지의 목적을 설명하십시오. 언제 반사를 사용 하시겠습니까? 성능의 영향은 무엇입니까?Mar 25, 2025 am 11:17 AM

이 기사는 코드의 런타임 조작, 직렬화, 일반 프로그래밍에 유리한 런타임 조작에 사용되는 GO의 반사 패키지에 대해 설명합니다. 실행 속도가 느리고 메모리 사용이 높아짐, 신중한 사용 및 최고와 같은 성능 비용을 경고합니다.

추적 도구를 사용하여 GO 응용 프로그램의 실행 흐름을 이해하려면 어떻게해야합니까?추적 도구를 사용하여 GO 응용 프로그램의 실행 흐름을 이해하려면 어떻게해야합니까?Mar 10, 2025 pm 05:36 PM

이 기사는 추적 도구를 사용하여 GO 응용 프로그램 실행 흐름을 분석합니다. 수동 및 자동 계측 기술, Jaeger, Zipkin 및 OpenTelemetry와 같은 도구 비교 및 ​​효과적인 데이터 시각화를 강조합니다.

GO에서 테이블 구동 테스트를 어떻게 사용합니까?GO에서 테이블 구동 테스트를 어떻게 사용합니까?Mar 21, 2025 pm 06:35 PM

이 기사는 테스트 케이스 테이블을 사용하여 여러 입력 및 결과로 기능을 테스트하는 방법 인 GO에서 테이블 중심 테스트를 사용하는 것에 대해 설명합니다. 가독성 향상, 중복 감소, 확장 성, 일관성 및 A와 같은 이점을 강조합니다.

See all articles

핫 AI 도구

Undresser.AI Undress

Undresser.AI Undress

사실적인 누드 사진을 만들기 위한 AI 기반 앱

AI Clothes Remover

AI Clothes Remover

사진에서 옷을 제거하는 온라인 AI 도구입니다.

Undress AI Tool

Undress AI Tool

무료로 이미지를 벗다

Clothoff.io

Clothoff.io

AI 옷 제거제

AI Hentai Generator

AI Hentai Generator

AI Hentai를 무료로 생성하십시오.

뜨거운 도구

Atom Editor Mac 버전 다운로드

Atom Editor Mac 버전 다운로드

가장 인기 있는 오픈 소스 편집기

드림위버 CS6

드림위버 CS6

시각적 웹 개발 도구

Dreamweaver Mac版

Dreamweaver Mac版

시각적 웹 개발 도구

메모장++7.3.1

메모장++7.3.1

사용하기 쉬운 무료 코드 편집기

MinGW - Windows용 미니멀리스트 GNU

MinGW - Windows용 미니멀리스트 GNU

이 프로젝트는 osdn.net/projects/mingw로 마이그레이션되는 중입니다. 계속해서 그곳에서 우리를 팔로우할 수 있습니다. MinGW: GCC(GNU Compiler Collection)의 기본 Windows 포트로, 기본 Windows 애플리케이션을 구축하기 위한 무료 배포 가능 가져오기 라이브러리 및 헤더 파일로 C99 기능을 지원하는 MSVC 런타임에 대한 확장이 포함되어 있습니다. 모든 MinGW 소프트웨어는 64비트 Windows 플랫폼에서 실행될 수 있습니다.