programing

바둑에서 문자열을 효율적으로 연결하는 방법

cafebook 2023. 4. 28. 21:30
반응형

바둑에서 문자열을 효율적으로 연결하는 방법

a 둑에서바, a.string는 원시 유형으로, 즉 읽기 전용이며, 이를 조작할 때마다 새 문자열이 생성됩니다.

따라서 결과 문자열의 길이를 알지 못하고 여러 번 문자열을 연결하려면 어떻게 하는 것이 가장 좋을까요?

단순한 방법은 다음과 같습니다.

var s string
for i := 0; i < 1000; i++ {
    s += getShortStringFromSomewhere()
}
return s

하지만 그것은 그다지 효율적이지 않아 보입니다.

새로운 방법:

1부터 바둑 1.10이 .strings.Builder유형, 자세한 내용은 이 답변을 참조하십시오.

이전 방법:

패키지를 사용합니다.그것은 구현하는 유형을 가지고 있습니다.

package main

import (
    "bytes"
    "fmt"
)

func main() {
    var buffer bytes.Buffer

    for i := 0; i < 1000; i++ {
        buffer.WriteString("a")
    }

    fmt.Println(buffer.String())
}

이렇게 하면 O(n) 시간 내에 완료됩니다.

Go 1.10+에는 다음이 있습니다.

작성기는 쓰기 메서드를 사용하여 효율적으로 문자열을 작성하는 데 사용됩니다.메모리 복사를 최소화합니다.0 값을 사용할 준비가 되었습니다.


그것은 거의 같습니다.bytes.Buffer.

package main

import (
    "strings"
    "fmt"
)

func main() {
    // ZERO-VALUE:
    //
    // It's ready to use from the get-go.
    // You don't need to initialize it.
    var sb strings.Builder

    for i := 0; i < 1000; i++ {
        sb.WriteString("a")
    }

    fmt.Println(sb.String())
}

놀이터에서 이것을 보려면 클릭하세요.


지원되는 인터페이스

StringBuilder의 메서드는 기존 인터페이스를 염두에 두고 구현되고 있습니다.코드에서 쉽게 새 Builder 유형으로 전환할 수 있습니다.

메서드 서명 인터페이스 묘사
Grow(int) bytes.Buffer 버퍼의 용량을 지정된 양만큼 늘립니다.바이트 참조.버퍼 #추가 정보를 보려면 확장하십시오.
Len() int bytes.Buffer 버퍼의 바이트 수를 반환합니다.바이트 참조.자세한 내용을 보려면 버퍼 #Len.
Reset() bytes.Buffer 버퍼를 빈 상태로 재설정합니다.바이트 참조.버퍼 #자세한 정보를 보려면 재설정합니다.
String() string fmt.Stringer 버퍼의 내용을 문자열로 반환합니다.fmt를 참조하십시오.자세한 내용을 보려면 문자열을 입력하십시오.
Write([]byte) (int, error) io.Writer 지정된 바이트를 버퍼에 씁니다.참고하세요.자세한 내용은 작성자를 참조하십시오.
WriteByte(byte) error io.ByteWriter 지정된 바이트를 버퍼에 씁니다.참고하세요.자세한 내용은 ByteWriter를 참조하십시오.
WriteRune(rune) (int, error) bufio.Writer또는bytes.Buffer 지정된 룬을 버퍼에 씁니다.부피오 참조.기록기 #WriteRune 또는 바이트입니다.자세한 내용을 보려면 버퍼 #WriteRune을 클릭하십시오.
WriteString(string) (int, error) io.stringWriter 지정된 문자열을 버퍼에 씁니다.자세한 내용은 io.stringWriter를 참조하십시오.

바이트와의 차이입니다.버퍼

  • 크기만 증가하거나 재설정할 수 있습니다.

  • 실수로 복사하는 것을 방지하는 copyCheck 메커니즘이 내장되어 있습니다.

    func (b *Builder) copyCheck() { ... }

  • bytes.Buffer 수 .(*Buffer).Bytes().

    • strings.Builder이 문제를 방지합니다.
    • 때때로, 이것은 문제가 되지 않고 대신 바라기도 합니다.
    • 예:바이트가 등으로 전달될 때의 훔쳐보기 동작입니다.
  • bytes.Buffer.Reset() 기본 버퍼를 되감고 재사용하는 반면에strings.Builder.Reset() 그렇지 않습니다. 버퍼를 분리합니다.


메모

  • StringBuilder 값은 기본 데이터를 캐시하므로 복사하지 마십시오.
  • StringBuilder 값을 공유하려면 해당 값에 대한 포인터를 사용합니다.

자세한 내용여기에서 소스 코드를 확인하십시오.

미리 할당할 문자열의 총 길이를 알고 있다면 문자열을 연결하는 가장 효율적인 방법은 기본 제공 함수를 사용하는 것일 수 있습니다.사전에 총 길이를 모를 경우 사용하지 마십시오.copy대신 다른 답을 읽어보세요.

테스트 결과, 이 접근 방식은 사용하는 것보다 최대 3배 빠르고 운영자를 사용하는 것보다 훨씬 빠릅니다(약 12,000배).+또한, 그것은 더 적은 메모리를 사용합니다.

이를 증명하기 위해 테스트 사례를 작성했으며 다음과 같은 결과가 있습니다.

BenchmarkConcat  1000000    64497 ns/op   502018 B/op   0 allocs/op
BenchmarkBuffer  100000000  15.5  ns/op   2 B/op        0 allocs/op
BenchmarkCopy    500000000  5.39  ns/op   0 B/op        0 allocs/op

다음은 테스트 코드입니다.

package main

import (
    "bytes"
    "strings"
    "testing"
)

func BenchmarkConcat(b *testing.B) {
    var str string
    for n := 0; n < b.N; n++ {
        str += "x"
    }
    b.StopTimer()

    if s := strings.Repeat("x", b.N); str != s {
        b.Errorf("unexpected result; got=%s, want=%s", str, s)
    }
}

func BenchmarkBuffer(b *testing.B) {
    var buffer bytes.Buffer
    for n := 0; n < b.N; n++ {
        buffer.WriteString("x")
    }
    b.StopTimer()

    if s := strings.Repeat("x", b.N); buffer.String() != s {
        b.Errorf("unexpected result; got=%s, want=%s", buffer.String(), s)
    }
}

func BenchmarkCopy(b *testing.B) {
    bs := make([]byte, b.N)
    bl := 0

    b.ResetTimer()
    for n := 0; n < b.N; n++ {
        bl += copy(bs[bl:], "x")
    }
    b.StopTimer()

    if s := strings.Repeat("x", b.N); string(bs) != s {
        b.Errorf("unexpected result; got=%s, want=%s", string(bs), s)
    }
}

// Go 1.10
func BenchmarkStringBuilder(b *testing.B) {
    var strBuilder strings.Builder

    b.ResetTimer()
    for n := 0; n < b.N; n++ {
        strBuilder.WriteString("x")
    }
    b.StopTimer()

    if s := strings.Repeat("x", b.N); strBuilder.String() != s {
        b.Errorf("unexpected result; got=%s, want=%s", strBuilder.String(), s)
    }
}

문자열로 효율적으로 변환할 문자열 조각이 있는 경우 이 방법을 사용할 수 있습니다.그렇지 않으면 다른 답을 살펴봅니다.

문자열 패키지에는 다음과 같은 라이브러리 함수가 있습니다.Joinhttp://golang.org/pkg/strings/ #가입

『 』 『 』 『 』의 .JoinKinopiko가 작성한 Append 함수와 유사한 접근 방식을 보여줍니다. https://golang.org/src/strings/strings.go#L420

용도:

import (
    "fmt";
    "strings";
)

func main() {
    s := []string{"this", "is", "a", "joined", "string\n"};
    fmt.Printf(strings.Join(s, " "));
}

$ ./test.bin
this is a joined string

에 제 워크 답변을 했는데, 는 는방재에나코상드게고했벤답시킹나위빠다니릅, 그순캣콘연다실보것가는로제자산단금만의기보다 빠릅니다.BufferString.

func (r *record) String() string {
    buffer := bytes.NewBufferString("");
    fmt.Fprint(buffer,"(",r.name,"[")
    for i := 0; i < len(r.subs); i++ {
        fmt.Fprint(buffer,"\t",r.subs[i])
    }
    fmt.Fprint(buffer,"]",r.size,")\n")
    return buffer.String()
}

이 작업은 0.81초가 걸렸지만 코드는 다음과 같습니다.

func (r *record) String() string {
    s := "(\"" + r.name + "\" ["
    for i := 0; i < len(r.subs); i++ {
        s += r.subs[i].String()
    }
    s += "] " + strconv.FormatInt(r.size,10) + ")\n"
    return s
} 

0.61초 밖에 걸리지 않았습니다.이것은 아마도 새로운 생성의 오버헤드 때문일 것입니다.BufferString.

업데이트: 또한 벤치마크했습니다.join되었습니다. 0.54초입니다.

func (r *record) String() string {
    var parts []string
    parts = append(parts, "(\"", r.name, "\" [" )
    for i := 0; i < len(r.subs); i++ {
        parts = append(parts, r.subs[i].String())
    }
    parts = append(parts, strconv.FormatInt(r.size,10), ")\n")
    return strings.Join(parts,"")
}
package main

import (
  "fmt"
)

func main() {
    var str1 = "string1"
    var str2 = "string2"
    out := fmt.Sprintf("%s %s ",str1, str2)
    fmt.Println(out)
}

이는 전체 버퍼 크기를 먼저 알거나 계산할 필요가 없는 가장 빠른 솔루션입니다.

var data []byte
for i := 0; i < 1000; i++ {
    data = append(data, getShortStringFromSomewhere()...)
}
return string(data)

벤치마크에 따르면 복사 솔루션(6.72ns가 아닌 추가당 8.1ns)보다 20% 느리지만 바이트를 사용하는 것보다 55% 더 빠릅니다.버퍼.

큰 바이트 조각을 만들고 문자열 조각을 사용하여 짧은 문자열의 바이트를 복사할 수 있습니다."Effective Go"에는 다음과 같은 기능이 있습니다.

func Append(slice, data[]byte) []byte {
    l := len(slice);
    if l + len(data) > cap(slice) { // reallocate
        // Allocate double what's needed, for future growth.
        newSlice := make([]byte, (l+len(data))*2);
        // Copy data (could use bytes.Copy()).
        for i, c := range slice {
            newSlice[i] = c
        }
        slice = newSlice;
    }
    slice = slice[0:l+len(data)];
    for i, c := range data {
        slice[l+i] = c
    }
    return slice;
}

그런다작완다사음용니합다을면료되이업음다사니▁then▁use합용ations▁are다음▁the를 사용합니다.string ( )바이트의 큰 조각에서 다시 문자열로 변환합니다.

2018년 노트 추가

Go 1.10부터는 다음과 같습니다.strings.Builder유형, 자세한 내용은 이 답변을 참조하십시오.

201x 이전 답변

@cd1의 벤치마크 코드와 다른 답변들이 틀렸습니다. b.N벤치마크 기능에는 설정할 수 없습니다.테스트 실행 시간이 안정적인지 판단하기 위해 테스트 도구에 의해 동적으로 설정됩니다.

벤치마크 함수는 동일한 테스트를 실행해야 합니다.b.N루프 내부의 테스트와 시간은 각 반복에 대해 동일해야 합니다.그래서 저는 내부 루프를 추가하여 고정합니다.다음과 같은 다른 솔루션에 대한 벤치마크도 추가합니다.

package main

import (
    "bytes"
    "strings"
    "testing"
)

const (
    sss = "xfoasneobfasieongasbg"
    cnt = 10000
)

var (
    bbb      = []byte(sss)
    expected = strings.Repeat(sss, cnt)
)

func BenchmarkCopyPreAllocate(b *testing.B) {
    var result string
    for n := 0; n < b.N; n++ {
        bs := make([]byte, cnt*len(sss))
        bl := 0
        for i := 0; i < cnt; i++ {
            bl += copy(bs[bl:], sss)
        }
        result = string(bs)
    }
    b.StopTimer()
    if result != expected {
        b.Errorf("unexpected result; got=%s, want=%s", string(result), expected)
    }
}

func BenchmarkAppendPreAllocate(b *testing.B) {
    var result string
    for n := 0; n < b.N; n++ {
        data := make([]byte, 0, cnt*len(sss))
        for i := 0; i < cnt; i++ {
            data = append(data, sss...)
        }
        result = string(data)
    }
    b.StopTimer()
    if result != expected {
        b.Errorf("unexpected result; got=%s, want=%s", string(result), expected)
    }
}

func BenchmarkBufferPreAllocate(b *testing.B) {
    var result string
    for n := 0; n < b.N; n++ {
        buf := bytes.NewBuffer(make([]byte, 0, cnt*len(sss)))
        for i := 0; i < cnt; i++ {
            buf.WriteString(sss)
        }
        result = buf.String()
    }
    b.StopTimer()
    if result != expected {
        b.Errorf("unexpected result; got=%s, want=%s", string(result), expected)
    }
}

func BenchmarkCopy(b *testing.B) {
    var result string
    for n := 0; n < b.N; n++ {
        data := make([]byte, 0, 64) // same size as bootstrap array of bytes.Buffer
        for i := 0; i < cnt; i++ {
            off := len(data)
            if off+len(sss) > cap(data) {
                temp := make([]byte, 2*cap(data)+len(sss))
                copy(temp, data)
                data = temp
            }
            data = data[0 : off+len(sss)]
            copy(data[off:], sss)
        }
        result = string(data)
    }
    b.StopTimer()
    if result != expected {
        b.Errorf("unexpected result; got=%s, want=%s", string(result), expected)
    }
}

func BenchmarkAppend(b *testing.B) {
    var result string
    for n := 0; n < b.N; n++ {
        data := make([]byte, 0, 64)
        for i := 0; i < cnt; i++ {
            data = append(data, sss...)
        }
        result = string(data)
    }
    b.StopTimer()
    if result != expected {
        b.Errorf("unexpected result; got=%s, want=%s", string(result), expected)
    }
}

func BenchmarkBufferWrite(b *testing.B) {
    var result string
    for n := 0; n < b.N; n++ {
        var buf bytes.Buffer
        for i := 0; i < cnt; i++ {
            buf.Write(bbb)
        }
        result = buf.String()
    }
    b.StopTimer()
    if result != expected {
        b.Errorf("unexpected result; got=%s, want=%s", string(result), expected)
    }
}

func BenchmarkBufferWriteString(b *testing.B) {
    var result string
    for n := 0; n < b.N; n++ {
        var buf bytes.Buffer
        for i := 0; i < cnt; i++ {
            buf.WriteString(sss)
        }
        result = buf.String()
    }
    b.StopTimer()
    if result != expected {
        b.Errorf("unexpected result; got=%s, want=%s", string(result), expected)
    }
}

func BenchmarkConcat(b *testing.B) {
    var result string
    for n := 0; n < b.N; n++ {
        var str string
        for i := 0; i < cnt; i++ {
            str += sss
        }
        result = str
    }
    b.StopTimer()
    if result != expected {
        b.Errorf("unexpected result; got=%s, want=%s", string(result), expected)
    }
}

환경은 OS X 10.11.6, 2.2GHz Intel Core i7입니다.

테스트 결과:

BenchmarkCopyPreAllocate-8         20000             84208 ns/op          425984 B/op          2 allocs/op
BenchmarkAppendPreAllocate-8       10000            102859 ns/op          425984 B/op          2 allocs/op
BenchmarkBufferPreAllocate-8       10000            166407 ns/op          426096 B/op          3 allocs/op
BenchmarkCopy-8                    10000            160923 ns/op          933152 B/op         13 allocs/op
BenchmarkAppend-8                  10000            175508 ns/op         1332096 B/op         24 allocs/op
BenchmarkBufferWrite-8             10000            239886 ns/op          933266 B/op         14 allocs/op
BenchmarkBufferWriteString-8       10000            236432 ns/op          933266 B/op         14 allocs/op
BenchmarkConcat-8                     10         105603419 ns/op        1086685168 B/op    10000 allocs/op

결론:

  1. CopyPreAllocate가장 빠른 방법입니다.AppendPreAllocate1위에 가깝지만 코드를 작성하는 것이 더 쉽습니다.
  2. Concat속도와 메모리 사용량 모두에서 성능이 매우 좋지 않습니다.사용하지 마세요.
  3. Buffer#Write그리고.Buffer#WriteString@Dani-Br이 코멘트에서 말한 것과 달리 속도는 기본적으로 동일합니다.인 고하면string로 말로정입니다.[]byte바둑에서는 말이 됩니다.
  4. bytes으로 버는기으다같음은사솔다용니합과 같은 합니다.Copy여분의 부기와 다른 것들로.
  5. Copy그리고.Append64의 부트스트랩 크기를 사용합니다(바이트와 동일).버퍼
  6. Append메모리와 할당을 더 많이 사용합니다. 사용하는 성장 알고리즘과 관련이 있다고 생각합니다.메모리가 바이트만큼 빠르게 증가하지 않습니다.버퍼

제안:

  1. 원하는 를 사용합니다.Append또는AppendPreAllocate그것은 충분히 빠르고 사용하기 쉽습니다.
  2. 에는 버퍼읽쓰동시수하경우는야행을 사용합니다.bytes.Buffer물이야론. 그것이 그것을 위해 입니다.그것이 바로 그것을 위해 설계된 것입니다.

내 원래 제안은.

s12 := fmt.Sprint(s1,s2)

그러나 바이트를 사용한 위의 답변.버퍼 - WriteString()이 가장 효율적인 방법입니다.

나의 초기 제안은 반사와 타입 스위치를 사용합니다.및 참조
내가 순진하게 생각했던 것처럼 기본 유형을 위한 범용 Stringer() 인터페이스는 없습니다.

적어도 Sprint()는 내부적으로 바이트를 사용합니다.버퍼. 그래서.

`s12 := fmt.Sprint(s1,s2,s3,s4,...,s1000)`

메모리 할당 측면에서 허용됩니다.

=> 빠른 디버그 출력을 위해 스프린트() 연결을 사용할 수 있습니다.
=> 그렇지 않으면 바이트를 사용합니다.... WriteString 파일

cd1의 답변 확장:copy() 대신 append()를 사용할 수도 있습니다. append()는 더 큰 사전 프로비저닝을 수행하여 메모리 비용이 조금 더 들지만 시간을 절약합니다.당신의 상단에 벤치마크를 더 추가했습니다.로컬로 실행:

go test -bench=. -benchtime=100ms

Thinkpad T400s에서는 다음과 같은 성능을 제공합니다.

BenchmarkAppendEmpty    50000000         5.0 ns/op
BenchmarkAppendPrealloc 50000000         3.5 ns/op
BenchmarkCopy           20000000        10.2 ns/op

이것은 @cd1에서 제공하는 벤치마크의 실제 버전입니다.Go 1.8,linux x86_64 @ 및 @icza 및 @PickBoy가 언급한 버그 수정으로.

Bytes.Buffer 인입니다.7 통해 직접 문열 연다배 두더빠 름음보 ▁conc를 통한 직접 보다 배 +교환입니다.

package performance_test

import (
    "bytes"
    "fmt"
    "testing"
)

const (
    concatSteps = 100
)

func BenchmarkConcat(b *testing.B) {
    for n := 0; n < b.N; n++ {
        var str string
        for i := 0; i < concatSteps; i++ {
            str += "x"
        }
    }
}

func BenchmarkBuffer(b *testing.B) {
    for n := 0; n < b.N; n++ {
        var buffer bytes.Buffer
        for i := 0; i < concatSteps; i++ {
            buffer.WriteString("x")
        }
    }
}

시간:

BenchmarkConcat-4                             300000          6869 ns/op
BenchmarkBuffer-4                            1000000          1186 ns/op

통풍구결합 간격

 func JoinBetween(in []string, separator string, startIndex, endIndex int) string {
    if in == nil {
        return ""
    }

    noOfItems := endIndex - startIndex

    if noOfItems <= 0 {
        return EMPTY
    }

    var builder strings.Builder

    for i := startIndex; i < endIndex; i++ {
        if i > startIndex {
            builder.WriteString(separator)
        }
        builder.WriteString(in[i])
    }
    return builder.String()
}

다음을 사용하여 수행합니다.

package main

import (
    "fmt"
    "strings"
)

func main (){
    concatenation:= strings.Join([]string{"a","b","c"},"") //where second parameter is a separator. 
    fmt.Println(concatenation) //abc
}
package main

import (
"fmt"
)

func main() {
    var str1 = "string1"
    var str2 = "string2"
    result := make([]byte, 0)
    result = append(result, []byte(str1)...)
    result = append(result, []byte(str2)...)
    result = append(result, []byte(str1)...)
    result = append(result, []byte(str2)...)

    fmt.Println(string(result))
}

간단하고 소화하기 쉬운 솔루션.댓글에 있는 세부 정보.복사는 슬라이스 요소를 덮어씁니다.단일 단일 요소를 슬라이스하여 덮어쓰는 중입니다.

package main

import (
    "fmt"
)

var N int = 100000

func main() {
    slice1 := make([]rune, N, N)
    //Efficient with fast performance, Need pre-allocated memory
    //We can add a check if we reached the limit then increase capacity
    //using append, but would be fined for data copying to new array. Also append happens after the length of current slice.
    for i := 0; i < N; i++ {
        copy(slice1[i:i+1], []rune{'N'})
    }
    fmt.Println(slice1)

    //Simple but fast solution, Every time the slice capacity is reached we get a fine of effort that goes
    //in copying data to new array
    slice2 := []rune{}
    for i := 0; i <= N; i++ {
        slice2 = append(slice2, 'N')
    }
    fmt.Println(slice2)

}

메모리 할당 통계가 포함된 벤치마크 결과입니다.github에서 벤치마크 코드를 확인합니다.

끈을 사용합니다.성능을 최적화하는 빌더입니다.

go test -bench . -benchmem
goos: darwin
goarch: amd64
pkg: github.com/hechen0/goexp/exps
BenchmarkConcat-8                1000000             60213 ns/op          503992 B/op          1 allocs/op
BenchmarkBuffer-8               100000000               11.3 ns/op             2 B/op          0 allocs/op
BenchmarkCopy-8                 300000000                4.76 ns/op            0 B/op          0 allocs/op
BenchmarkStringBuilder-8        1000000000               4.14 ns/op            6 B/op          0 allocs/op
PASS
ok      github.com/hechen0/goexp/exps   70.071s
s := fmt.Sprintf("%s%s", []byte(s1), []byte(s2))

strings.Join()에 패키지에서

유형이 일치하지 않는 경우(예: int와 문자열을 결합하려는 경우) RANDOMTYPE(변경할 항목)을 수행합니다.

EX:

package main

import (
    "fmt"
    "strings"
)

var intEX = 0
var stringEX = "hello all you "
var stringEX2 = "people in here"


func main() {
    s := []string{stringEX, stringEX2}
    fmt.Println(strings.Join(s, ""))
}

출력:

hello all you people in here

언급URL : https://stackoverflow.com/questions/1760757/how-to-efficiently-concatenate-strings-in-go

반응형