我对Salvador的基准测试进行了修改,并添加了
strings.Join()
。他的答案仍然正确 - 对于大字符串,假设您想保留原始字符串,
str[:index] + string(replacement) + str[index+1:]
是最快的选择。对于小字符串来说,
strings.Join()
也很接近,对于大字符串来说则非常接近。我还使用更大的字符串添加了测试,以查看是否在任何时候
strings.Join()
会变得更快 - 看起来似乎不是这样。
此外,仅仅为了好玩,我用
unsafe
和
reflect
编写了另外两个实现。
- 其中一个进行了复制,因此它不会修改原始字符串,并且具有非常有趣的性能结果 - 对于小字符串来说更快,对于中等大小的字符串来说要快得多,但对于大字符串来说要慢得多。
- 另一个只是使用可变字符串,并且在所有情况下都显著快得多,并且以恒定时间运行,但要注意它会修改原始字符串。
我还必须修改
generateString()
,以便生成的最大字符串可以在合理的时间内生成 ;)
无论如何,以下是代码:
package main
import (
"reflect"
"strings"
"testing"
"unsafe"
)
func replaceAtIndex1(str string, replacement rune, index int) string {
out := []rune(str)
out[index] = replacement
return string(out)
}
func replaceAtIndex2(str string, replacement rune, index int) string {
return str[:index] + string(replacement) + str[index+1:]
}
func replaceAtIndex3(str string, replacement rune, index int) string {
return strings.Join([]string{str[:index], str[index + 1:]}, string(replacement))
}
func strToBytes(str string) []byte {
string_header := (*reflect.StringHeader)(unsafe.Pointer(&str))
bytes_header := &reflect.SliceHeader{
Data : string_header.Data,
Len : string_header.Len,
Cap : string_header.Len,
}
return *(*[]byte)(unsafe.Pointer(bytes_header))
}
func strToBytesCopy(str string) []byte {
bytes_unsafe := strToBytes(str)
bytes := make([]byte, len(bytes_unsafe))
copy(bytes, bytes_unsafe)
return bytes
}
func bytesToStr(bytes []byte) string {
bytes_header := (*reflect.SliceHeader)(unsafe.Pointer(&bytes))
string_header := &reflect.StringHeader{
Data : bytes_header.Data,
Len : bytes_header.Len,
}
return *(*string)(unsafe.Pointer(string_header))
}
func replaceAtIndex4(str string, replacement rune, index int) string {
bytes := strToBytesCopy(str)
bytes[index] = byte(replacement)
return bytesToStr(bytes)
}
func replaceAtIndex5(str string, replacement rune, index int) string {
bytes := strToBytes(str)
bytes[index] = byte(replacement)
return bytesToStr(bytes)
}
func generateString(n int) string{
var b strings.Builder
b.Grow(n)
for i := 0; i < n; i++ {
b.WriteRune('a')
}
return b.String()
}
func BenchmarkSmall1(b *testing.B) {
n := 10
str, index, replacement := generateString(n), n / 2, 'B'
b.ResetTimer()
for i := 0; i < b.N; i++ {
replaceAtIndex1(str, replacement, index)
}
}
func BenchmarkSmall2(b *testing.B) {
n := 10
str, index, replacement := generateString(n), n / 2, 'B'
b.ResetTimer()
for i := 0; i < b.N; i++ {
replaceAtIndex2(str, replacement, index)
}
}
func BenchmarkSmall3(b *testing.B) {
n := 10
str, index, replacement := generateString(n), n / 2, 'B'
b.ResetTimer()
for i := 0; i < b.N; i++ {
replaceAtIndex3(str, replacement, index)
}
}
func BenchmarkSmall4(b *testing.B) {
n := 10
str, index, replacement := generateString(n), n / 2, 'B'
b.ResetTimer()
for i := 0; i < b.N; i++ {
replaceAtIndex4(str, replacement, index)
}
}
func BenchmarkSmall5(b *testing.B) {
n := 10
str, index, replacement := generateString(n), n / 2, 'B'
b.ResetTimer()
for i := 0; i < b.N; i++ {
replaceAtIndex5(str, replacement, index)
}
}
func BenchmarkMedium1(b *testing.B) {
n := 100
str, index, replacement := generateString(n), n / 2, 'B'
b.ResetTimer()
for i := 0; i < b.N; i++ {
replaceAtIndex1(str, replacement, index)
}
}
func BenchmarkMedium2(b *testing.B) {
n := 100
str, index, replacement := generateString(n), n / 2, 'B'
b.ResetTimer()
for i := 0; i < b.N; i++ {
replaceAtIndex2(str, replacement, index)
}
}
func BenchmarkMedium3(b *testing.B) {
n := 100
str, index, replacement := generateString(n), n / 2, 'B'
b.ResetTimer()
for i := 0; i < b.N; i++ {
replaceAtIndex3(str, replacement, index)
}
}
func BenchmarkMedium4(b *testing.B) {
n := 100
str, index, replacement := generateString(n), n / 2, 'B'
b.ResetTimer()
for i := 0; i < b.N; i++ {
replaceAtIndex4(str, replacement, index)
}
}
func BenchmarkMedium5(b *testing.B) {
n := 100
str, index, replacement := generateString(n), n / 2, 'B'
b.ResetTimer()
for i := 0; i < b.N; i++ {
replaceAtIndex5(str, replacement, index)
}
}
func BenchmarkBig1(b *testing.B) {
n := 10000
str, index, replacement := generateString(n), n / 2, 'B'
b.ResetTimer()
for i := 0; i < b.N; i++ {
replaceAtIndex1(str, replacement, index)
}
}
func BenchmarkBig2(b *testing.B) {
n := 10000
str, index, replacement := generateString(n), n / 2, 'B'
b.ResetTimer()
for i := 0; i < b.N; i++ {
replaceAtIndex2(str, replacement, index)
}
}
func BenchmarkBig3(b *testing.B) {
n := 10000
str, index, replacement := generateString(n), n / 2, 'B'
b.ResetTimer()
for i := 0; i < b.N; i++ {
replaceAtIndex3(str, replacement, index)
}
}
func BenchmarkBig4(b *testing.B) {
n := 10000
str, index, replacement := generateString(n), n / 2, 'B'
b.ResetTimer()
for i := 0; i < b.N; i++ {
replaceAtIndex4(str, replacement, index)
}
}
func BenchmarkBig5(b *testing.B) {
n := 10000
str, index, replacement := generateString(n), n / 2, 'B'
b.ResetTimer()
for i := 0; i < b.N; i++ {
replaceAtIndex5(str, replacement, index)
}
}
func BenchmarkHuge2(b *testing.B) {
n := 100000
str, index, replacement := generateString(n), n / 2, 'B'
b.ResetTimer()
for i := 0; i < b.N; i++ {
replaceAtIndex2(str, replacement, index)
}
}
func BenchmarkHuge3(b *testing.B) {
n := 100000
str, index, replacement := generateString(n), n / 2, 'B'
b.ResetTimer()
for i := 0; i < b.N; i++ {
replaceAtIndex3(str, replacement, index)
}
}
func BenchmarkHuge4(b *testing.B) {
n := 100000
str, index, replacement := generateString(n), n / 2, 'B'
b.ResetTimer()
for i := 0; i < b.N; i++ {
replaceAtIndex4(str, replacement, index)
}
}
func BenchmarkHuge5(b *testing.B) {
n := 100000
str, index, replacement := generateString(n), n / 2, 'B'
b.ResetTimer()
for i := 0; i < b.N; i++ {
replaceAtIndex5(str, replacement, index)
}
}
func BenchmarkGargantuan2(b *testing.B) {
n := 10000000
str, index, replacement := generateString(n), n / 2, 'B'
b.ResetTimer()
for i := 0; i < b.N; i++ {
replaceAtIndex2(str, replacement, index)
}
}
func BenchmarkGargantuan3(b *testing.B) {
n := 10000000
str, index, replacement := generateString(n), n / 2, 'B'
b.ResetTimer()
for i := 0; i < b.N; i++ {
replaceAtIndex3(str, replacement, index)
}
}
func BenchmarkGargantuan4(b *testing.B) {
n := 10000000
str, index, replacement := generateString(n), n / 2, 'B'
b.ResetTimer()
for i := 0; i < b.N; i++ {
replaceAtIndex4(str, replacement, index)
}
}
func BenchmarkGargantuan5(b *testing.B) {
n := 10000000
str, index, replacement := generateString(n), n / 2, 'B'
b.ResetTimer()
for i := 0; i < b.N; i++ {
replaceAtIndex5(str, replacement, index)
}
}
func main(){}
以下是结果:
BenchmarkSmall1-8 20000000 99.9 ns/op
BenchmarkSmall2-8 50000000 29.5 ns/op
BenchmarkSmall3-8 20000000 58.1 ns/op
BenchmarkSmall4-8 50000000 32.0 ns/op
BenchmarkSmall5-8 1000000000 2.93 ns/op
BenchmarkMedium1-8 1000000 1034 ns/op
BenchmarkMedium2-8 20000000 68.4 ns/op
BenchmarkMedium3-8 20000000 78.8 ns/op
BenchmarkMedium4-8 30000000 49.3 ns/op
BenchmarkMedium5-8 1000000000 3.02 ns/op
BenchmarkBig1-8 20000 89557 ns/op
BenchmarkBig2-8 1000000 1204 ns/op
BenchmarkBig3-8 1000000 1257 ns/op
BenchmarkBig4-8 1000000 1200 ns/op
BenchmarkBig5-8 1000000000 2.93 ns/op
BenchmarkHuge2-8 200000 10260 ns/op
BenchmarkHuge3-8 200000 9908 ns/op
BenchmarkHuge4-8 100000 13628 ns/op
BenchmarkHuge5-8 1000000000 2.99 ns/op
BenchmarkGargantuan2-8 2000 822881 ns/op
BenchmarkGargantuan3-8 2000 807522 ns/op
BenchmarkGargantuan4-8 1000 2148387 ns/op
BenchmarkGargantuan5-8 1000000000 2.96 ns/op