[Go] Некоторые мысли об эффективном перехвате строк

Go

Оригинальная ссылка:blog.thinker idea.com/201910/go/ о…

Недавно я былGo Forumнайти в[SOLVED] String size of 20 characterЭта проблема,"hollowaykeanho" дал соответствующий ответ, и я обнаружил, что схема перехвата строк не самый идеальный метод, поэтому я провел серию экспериментов и получил эффективный метод перехвата строк. Эта статья шаг за шагом объяснит процесс моей практики .

Перехват байтового среза

Это точно "hollowaykeanhoПервое решение, данное ", я думаю, что это также первое решение, о котором думают многие, использует встроенный синтаксис среза go для перехвата строк:

s := "abcdef"
fmt.Println(s[1:4])

Мы быстро поняли, что это побайтовое усечение, при обработкеASCIIНет лучшего решения, чем перехват однобайтовой строки, китайский часто занимает несколько байтов.utf8В кодировке 3 байта, мы получим искаженные данные в следующей программе:

s := "Go 语言"
fmt.Println(s[1:4])

Убийца - преобразование типа []руна

"hollowaykeanhoВторое приведенное решение — преобразовать строку в[]rune, а затем перехватить в соответствии с синтаксисом среза, а затем преобразовать результат в строку.

s := "Go 语言"
rs := []rune(s)
fmt.Println(strings(rs[1:4]))

Сначала мы получили правильный результат, что является самым большим улучшением. Тем не менее, я всегда осторожно относился к преобразованию типов и беспокоился о его производительности, поэтому я пытался найти ответ в поисковых системах и на крупных форумах, но самое большее, что я нашел, это это решение, которое кажется единственным решением.

Я пытаюсь написать тест производительности, чтобы оценить его производительность:

package benchmark

import (
	"testing"
)

var benchmarkSubString = "Go语言是Google开发的一种静态强类型、编译型、并发型,并具有垃圾回收功能的编程语言。为了方便搜索和识别,有时会将其称为Golang。"
var benchmarkSubStringLength = 20

func SubStrRunes(s string, length int) string {
	if utf8.RuneCountInString(s) > length {
		rs := []rune(s)
		return string(rs[:length])
	}

	return s
}

func BenchmarkSubStrRunes(b *testing.B) {
	for i := 0; i < b.N; i++ {
		SubStrRunes(benchmarkSubString, benchmarkSubStringLength)
	}
}

Я получил результаты, которые меня немного удивили:

goos: darwin
goarch: amd64
pkg: github.com/thinkeridea/go-extend/exunicode/exutf8/benchmark
BenchmarkSubStrRunes-8            872253              1363 ns/op             336 B/op          2 allocs/op
PASS
ok      github.com/thinkeridea/go-extend/exunicode/exutf8/benchmark     2.120s

Для усечения первых 20 символов строки из 69 строк требуется около 1,3 микросекунды, что намного превышает мои ожидания.Я обнаружил, что из-за выделения памяти, вызванного преобразованием типа, создается новая строка, а преобразование типа требует много вычислений.

Спасатель — utf8.DecodeRuneInString

Я хочу улучшить дополнительные операции и выделение памяти, вызванные преобразованием типов, я тщательно прочесал этоstringspackage и обнаружил, что нет связанного инструмента, тогда я подумал об этомutf8пакет, который предоставляет инструменты, связанные с многобайтовыми вычислениями. Честно говоря, я не знаком с ним или активно (непосредственно) не использовал его. Я проверил всю его документацию и обнаружил, чтоutf8.DecodeRuneInStringФункция может преобразовать одиночный символ и выдать количество байтов, занимаемых символом.Я пробовал следующие эксперименты:

package benchmark

import (
	"testing"
	"unicode/utf8"
)

var benchmarkSubString = "Go语言是Google开发的一种静态强类型、编译型、并发型,并具有垃圾回收功能的编程语言。为了方便搜索和识别,有时会将其称为Golang。"
var benchmarkSubStringLength = 20

func SubStrDecodeRuneInString(s string, length int) string {
	var size, n int
	for i := 0; i < length && n < len(s); i++ {
		_, size = utf8.DecodeRuneInString(s[n:])
		n += size
	}

	return s[:n]
}

func BenchmarkSubStrDecodeRuneInString(b *testing.B) {
	for i := 0; i < b.N; i++ {
		SubStrDecodeRuneInString(benchmarkSubString, benchmarkSubStringLength)
	}
}

После запуска я получил к своему удивлению:

goos: darwin
goarch: amd64
pkg: github.com/thinkeridea/go-extend/exunicode/exutf8/benchmark
BenchmarkSubStrDecodeRuneInString-8     10774401               105 ns/op               0 B/op          0 allocs/op
PASS
ok      github.com/thinkeridea/go-extend/exunicode/exutf8/benchmark     1.250s

Сравнивать[]runeУлучшена эффективность преобразования типов13 раз, устраняет выделение памяти, это действительно захватывающе и захватывающе, мне не терпится ответить"hollowaykeanho«Скажите ему, что я нашел лучший способ, и предоставьте соответствующие тесты производительности.

Я был немного взволнован, и взволнованно просматривал всевозможные интересные вопросы на форуме.Просматривая справку по вопросу (забыл, какой вопрос -_-||), я был удивлен, обнаружив другую идею.

Хорошее лекарство не должно быть горьким — итерация строки диапазона

Многие видимо забылиrangeИтерации по символам, а не по байтам. использоватьrangeПри переборе строки он возвращает начальный индекс символа и соответствующий символ.Я сразу попытался использовать эту функцию, чтобы написать следующий вариант использования:

package benchmark

import (
	"testing"
)

var benchmarkSubString = "Go语言是Google开发的一种静态强类型、编译型、并发型,并具有垃圾回收功能的编程语言。为了方便搜索和识别,有时会将其称为Golang。"
var benchmarkSubStringLength = 20

func SubStrRange(s string, length int) string {
	var n, i int
	for i = range s {
		if n == length {
			break
		}

		n++
	}

	return s[:i]
}

func BenchmarkSubStrRange(b *testing.B) {
	for i := 0; i < b.N; i++ {
		SubStrRange(benchmarkSubString, benchmarkSubStringLength)
	}
}

Я попробовал запустить его, и это казалось бесконечным волшебством, и оно меня не разочаровало.

goos: darwin
goarch: amd64
pkg: github.com/thinkeridea/go-extend/exunicode/exutf8/benchmark
BenchmarkSubStrRange-8          12354991                91.3 ns/op             0 B/op          0 allocs/op
PASS
ok      github.com/thinkeridea/go-extend/exunicode/exutf8/benchmark     1.233s

Улучшение всего на 13%, но оно настолько простое и понятное, что похоже на лекарство, которое я искал.

Если вы думали, что это конец, то нет, для меня это только начало исследования.

Окончательный момент - создайте свои собственные колеса

напитокrangeЭта миска сладкого и жирного лекарства, кажется, успокаивает меня, мне нужно построить колесо, оно должно быть проще в использовании и более эффективным.

Итак, я внимательно наблюдал за двумя схемами оптимизации, все они, похоже, заключаются в том, чтобы найти позицию индекса перехватываемых символов заданной длины, если я могу предоставить такой метод, могу ли я предоставить пользователям простую реализацию перехватаs[:strIndex(20)], я не мог избавиться от этой идеи, как только она зародилась, и я два дня бился над тем, как обеспечить простой в использовании интерфейс.

После этого я создалexutf8.RuneIndexInStringа такжеexutf8.RuneIndexметод, используемый для вычисления позиции конечного индекса указанного количества символов в строке и байтовом срезе соответственно.

я используюexutf8.RuneIndexInStringРеализован тест на перехват строк:

package benchmark

import (
	"testing"
	"unicode/utf8"

	"github.com/thinkeridea/go-extend/exunicode/exutf8"
)

var benchmarkSubString = "Go语言是Google开发的一种静态强类型、编译型、并发型,并具有垃圾回收功能的编程语言。为了方便搜索和识别,有时会将其称为Golang。"
var benchmarkSubStringLength = 20

func SubStrRuneIndexInString(s string, length int) string {
	n, _ := exutf8.RuneIndexInString(s, length)
	return s[:n]
}

func BenchmarkSubStrRuneIndexInString(b *testing.B) {
	for i := 0; i < b.N; i++ {
		SubStrRuneIndexInString(benchmarkSubString, benchmarkSubStringLength)
	}
}

Попробуйте запустить его, и я очень доволен результатами:

goos: darwin
goarch: amd64
pkg: github.com/thinkeridea/go-extend/exunicode/exutf8/benchmark
BenchmarkSubStrRuneIndexInString-8      13546849                82.4 ns/op             0 B/op          0 allocs/op
PASS
ok      github.com/thinkeridea/go-extend/exunicode/exutf8/benchmark     1.213s

лучшая производительностьrangeПовышение на 10%, и я с облегчением снова получил новое усиление, доказав, что оно работает.

Он достаточно эффективен, но не прост в использовании. Мне нужно две строки кода для перехвата строк. Если я хочу перехватывать символы от 10 до 20, мне нужно 4 строки кода. Это не простой в использовании интерфейс для пользователей. Я имею в виду другие лингвистическиеsub_stringметода, я думаю, что мне также следует разработать такой интерфейс для пользователя.

exutf8.RuneSubStringа такжеexutf8.RuneSubЭто метод, который я написал после тщательного обдумывания:

func RuneSubString(s string, start, length int) string

Он имеет три параметра:

  • s: строка ввода
  • start: Позиция начала перехвата. Если start неотрицательное число, возвращаемая строка будет начинаться с начальной позиции строки и начинать отсчет с 0. Например, в строке «abcdef» символ в позиции 0 — это «a», символ в позиции 2 — «c» и так далее. Если значение start отрицательно, возвращаемая строка будет начинаться с начального символа с конца строки. Если длина строки меньше start, будет возвращена пустая строка.
  • length: Длина, которую нужно урезать. Если указана положительная длина, возвращаемая строка будет начинаться с начала и включать символы до длины (в зависимости от длины строки). Если указана отрицательная длина, то символы длины в конце строки будут опущены (если начало отрицательное, то считается с конца строки). Если start не находится в этом тексте, будет возвращена пустая строка. Если указана длина 0, возвращаемая подстрока будет начинаться с начальной позиции до конца строки.

Я предоставил им псевдонимы, и каждый более склонен использовать их в соответствии со своими привычками использования.stringsпакет, ищущий решение такого рода проблемы, я создалexstrings.SubStringа такжеexbytes.SubКак более легко извлекаемый метод псевдонима.

Наконец, мне нужно сделать еще один тест производительности, чтобы убедиться в его производительности:

package benchmark

import (
	"testing"

	"github.com/thinkeridea/go-extend/exunicode/exutf8"
)

var benchmarkSubString = "Go语言是Google开发的一种静态强类型、编译型、并发型,并具有垃圾回收功能的编程语言。为了方便搜索和识别,有时会将其称为Golang。"
var benchmarkSubStringLength = 20

func SubStrRuneSubString(s string, length int) string {
	return exutf8.RuneSubString(s, 0, length)
}

func BenchmarkSubStrRuneSubString(b *testing.B) {
	for i := 0; i < b.N; i++ {
		SubStrRuneSubString(benchmarkSubString, benchmarkSubStringLength)
	}
}

Запустите его, он меня не подводит:

goos: darwin
goarch: amd64
pkg: github.com/thinkeridea/go-extend/exunicode/exutf8/benchmark
BenchmarkSubStrRuneSubString-8          13309082                83.9 ns/op             0 B/op          0 allocs/op
PASS
ok      github.com/thinkeridea/go-extend/exunicode/exutf8/benchmark     1.215s

Хотя по сравнению сexutf8.RuneIndexInStringОт него отказались, но он предоставляет интерфейс, с которым легко взаимодействовать и использовать. Я думаю, что это должно быть наиболее практичным решением. Его все еще можно использовать, если вы стремитесь к конечному результату.exutf8.RuneIndexInString, это по-прежнему самое быстрое решение.

Суммировать

Когда вы видите код, о котором идет речь, даже если он очень простой, его все равно стоит заглянуть и изучить, это не скучно и скучно, но это будет очень полезно.

с самого начала[]runeПреобразование типа, чтобы, наконец, построить свои собственные колеса, не только получить16 разулучшение производительности, я также узналutf8Упаковать и углубитьrangeОбход свойств строки и дляgo-extendСклад содержит ряд практичных и эффективных решений, позволяющих болееgo-extendпользователи получают результаты.

go-extendЭто репозиторий, содержащий практические и эффективные методы.Если у читателей есть хорошие функции и общие и эффективные решения, я надеюсь, что вы без колебаний пришлете их мне.Pull request, вы также можете использовать этот репозиторий для ускорения реализации функций и повышения производительности.

Перепечатано:

Автор этой статьи: Ци Инь (thinkeridea)

Ссылка на эту статью:blog.thinker idea.com/201910/go/ о…

Заявление об авторских правах: все статьи в этом блоге используются, если не указано иное.Соглашение CC BY 4.0 CNсоглашение. Пожалуйста, укажите источник!