0

4 Commits
v1.8 ... v1.9

19 changed files with 442 additions and 534 deletions

View File

@@ -2,19 +2,75 @@
GOOPTS=CGO_ENABLED=0 GOOPTS=CGO_ENABLED=0
BUILDOPTS=-ldflags="-s -w" -a -gcflags=all=-l -trimpath -buildvcs=false BUILDOPTS=-ldflags="-s -w" -a -gcflags=all=-l -trimpath -buildvcs=false
BINARY=aleesa-misc-go MYNAME=aleesa-misc-go
BINARY=${MYNAME}
UNIX_BINARY=${MYNAME}
WINDOWS_BINARY=${MYNAME}.exe
RMCMD=rm -rf
# На windows имя бинарника может зависеть не только от платформы, но и от выбранной цели, для linux-а суффикс .exe
# не нужен
ifeq ($(OS),Windows_NT)
ifdef GOOS
ifeq ($(GOOS),windows)
BINARY=${WINDOWS_BINARY}
else # not ifeq ($(GOOS),windows)
BINARY=${MYNAME}
endif # ifeq ($(GOOS),windows)
else # not ifdef GOOS
BINARY=${WINDOWS_BINARY}
endif # ifdef GOOS
ifeq ($(SHELL), sh.exe)
RMCMD=DEL /Q /F
endif
endif
# Явно определяем символ новой строки, чтобы избежать неоднозначности на windows
define IFS
endef
all: clean build all: clean build
build: build:
${GOOPTS} go build ${BUILDOPTS} -o ${BINARY} ./cmd/${BINARY} ifeq ($(OS),Windows_NT)
# Looks like on windows gnu make explicitly set SHELL to sh.exe, if it was not set.
ifeq ($(SHELL), sh.exe)
# # Vanilla cmd.exe / powershell.
SET "CGO_ENABLED=0"
go build ${BUILDOPTS} -o ${BINARY} ./cmd/${MYNAME}
else ifeq (,$(findstring(Git/usr/bin/sh.exe, $(SHELL))))
# # git-bash
CGO_ENABLED=0 go build ${BUILDOPTS} -o ${BINARY} ./cmd/${MYNAME}
else # not ifeq (,$(findstring(Git/usr/bin/sh.exe, $(SHELL))))
# # Some other shell.
# # TODO: handle it.
$(info "-- Dunno how to handle this shell: ${SHELL}")
endif # ifeq (,$(findstring(Git/usr/bin/sh.exe, $(SHELL))))
else # not ($(OS),Windows_NT)
CGO_ENABLED=0 go build ${BUILDOPTS} -o ${BINARY} ./cmd/${MYNAME}
endif # ifeq ($(OS),Windows_NT)
clean: clean:
rm -rf ${BINARY} ifeq ($(OS),Windows_NT)
ifeq ($(SHELL),sh.exe)
# # Vanilla cmd.exe / powershell.
if exist ${WINDOWS_BINARY} ${RMCMD} ${WINDOWS_BINARY}
if exist ${UNIX_BINARY} ${RMCMD} ${UNIX_BINARY}
else # not ifeq ($(SHELL),sh.exe)
${RMCMD} ./${WINDOWS_BINARY}
${RMCMD} ./${UNIX_BINARY}
endif # ifeq ($(SHELL),sh.exe)
else # not ifeq ($(OS),Windows_NT)
${RMCMD} ./${BINARY}
endif
upgrade: upgrade:
rm -rf vendor go get -u ./...
go get -u -t ./...
go mod tidy go mod tidy
go mod vendor go mod vendor

View File

@@ -17,9 +17,9 @@
## Как это собрать? ## Как это собрать?
Понадобится компилятор go версии 1.22 или более новый. Понадобится компилятор go версии 1.25.1 или более новый.
Сам процесс сборки простой, достаточно запустить: Сам процесс сборки простой, достаточно запустить make или gmake, в зависимости от платформы:
```bash ```bash
make make

View File

@@ -2,12 +2,12 @@ package main
import ( import (
"fmt" "fmt"
"log/slog"
"os" "os"
"os/signal" "os/signal"
"syscall" "syscall"
"time" "time"
"aleesa-misc-go/internal/log"
"aleesa-misc-go/internal/misc" "aleesa-misc-go/internal/misc"
"github.com/go-redis/redis/v8" "github.com/go-redis/redis/v8"
@@ -15,71 +15,24 @@ import (
func main() { func main() {
var ( var (
err error err error
loglevel slog.Level logfile *os.File
) )
misc.ReadConfig() misc.ReadConfig()
loghandler := os.Stderr
if misc.Config.Log != "" { if misc.Config.Log != "" {
loghandler, err = os.OpenFile(misc.Config.Log, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644) logfile, err = os.OpenFile(misc.Config.Log, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644)
if err != nil { if err != nil {
slog.Error(fmt.Sprintf("Unable to open log file %s: %s", misc.Config.Log, err)) log.Error(fmt.Sprintf("Unable to open log file %s: %s", misc.Config.Log, err))
os.Exit(1) os.Exit(1)
} }
} else {
logfile = os.Stderr
} }
// no panic, no trace. log.Init(misc.Config.Loglevel, logfile)
switch misc.Config.Loglevel {
case "error":
loglevel = slog.LevelError
case "warn":
loglevel = slog.LevelWarn
case "info":
loglevel = slog.LevelInfo
case "debug":
loglevel = slog.LevelDebug
default:
loglevel = slog.LevelInfo
}
opts := &slog.HandlerOptions{
// Use the ReplaceAttr function on the handler options
// to be able to replace any single attribute in the log output
ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr {
// check that we are handling the time key
if a.Key != slog.TimeKey {
return a
}
t := a.Value.Time()
// change the value from a time.Time to a String
// where the string has the correct time format.
a.Value = slog.StringValue(t.Format(time.DateTime))
return a
},
Level: loglevel,
}
slog.SetDefault(
slog.New(
slog.NewTextHandler(
loghandler,
opts,
),
),
)
// Иницализируем клиента Редиски. // Иницализируем клиента Редиски.
misc.RedisClient = redis.NewClient(&redis.Options{ misc.RedisClient = redis.NewClient(&redis.Options{
@@ -100,7 +53,7 @@ func main() {
// Начнём выгребать события из редиски (длина конвеера/буфера канала по-умолчанию - 100 сообщений). // Начнём выгребать события из редиски (длина конвеера/буфера канала по-умолчанию - 100 сообщений).
ch := misc.Subscriber.Channel() ch := misc.Subscriber.Channel()
slog.Info("Aleesa-misc-go started") log.Info("Aleesa-misc-go started")
for msg := range ch { for msg := range ch {
if !misc.Shutdown { if !misc.Shutdown {

16
go.mod
View File

@@ -1,15 +1,13 @@
module aleesa-misc-go module aleesa-misc-go
go 1.24.0 go 1.25.1
require github.com/go-redis/redis/v8 v8.11.5
require ( require (
github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/go-redis/redis/v8 v8.11.5
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/hjson/hjson-go v3.3.0+incompatible
golang.org/x/sys v0.35.0 // indirect
) )
require github.com/hjson/hjson-go v3.3.0+incompatible require (
github.com/cespare/xxhash/v2 v2.1.2 // indirect
tool aleesa-misc-go github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
)

8
go.sum
View File

@@ -1,5 +1,5 @@
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
@@ -16,8 +16,8 @@ github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE=
github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs= github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs=
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 h1:DzZ89McO9/gWPsQXS/FVKAlG02ZjaQ6AlZRBimEYOd0= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 h1:DzZ89McO9/gWPsQXS/FVKAlG02ZjaQ6AlZRBimEYOd0=
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM=
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=

106
internal/log/log.go Normal file
View File

@@ -0,0 +1,106 @@
package log
import (
"fmt"
"log/slog"
"os"
"time"
)
// Init setup logger stuff.
// level can be error, warn, info, debug if something other supplied info level selected.
// fileDescriptor should be opened before supplying it to Init().
func Init(level string, fileDescriptor *os.File) {
var loglevel slog.Level
// no panic, no trace.
switch level {
case "error":
loglevel = slog.LevelError
case "warn":
loglevel = slog.LevelWarn
case "info":
loglevel = slog.LevelInfo
case "debug":
loglevel = slog.LevelDebug
default:
loglevel = slog.LevelInfo
}
opts := &slog.HandlerOptions{
// Use the ReplaceAttr function on the handler options
// to be able to replace any single attribute in the log output
ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr {
// check that we are handling the time key
if a.Key != slog.TimeKey {
return a
}
t := a.Value.Time()
// change the value from a time.Time to a String
// where the string has the correct time format.
a.Value = slog.StringValue(t.Format(time.DateTime))
return a
},
Level: loglevel,
}
slog.SetDefault(
slog.New(
slog.NewTextHandler(
fileDescriptor,
opts,
),
),
)
}
// Error passes message directly to slog.Error().
func Error(message string) {
slog.Error(message)
}
// Errorf logs message on error log level and allows to supply arguments in printf() style.
func Errorf(format string, a ...any) {
slog.Error(fmt.Sprintf(format, a...))
}
// Warn passes message directly to slog.Warn().
func Warn(message string) {
slog.Warn(message)
}
// Warnf logs message on warn log level and allows to supply arguments in printf() style.
func Warnf(format string, a ...any) {
slog.Warn(fmt.Sprintf(format, a...))
}
// Info passes message directly to slog.Info().
func Info(message string) {
slog.Info(message)
}
// Infof logs message on info log level and allows to supply arguments in printf() style.
func Infof(format string, a ...any) {
slog.Info(fmt.Sprintf(format, a...))
}
// Debug passes message directly to slog.Debug().
func Debug(message string) {
slog.Debug(message)
}
// Debugf logs message on debug log level and allows to supply arguments in printf() style.
func Debugf(format string, a ...any) {
slog.Debug(fmt.Sprintf(format, a...))
}
/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */

View File

@@ -3,9 +3,9 @@ package misc
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"fmt"
"log/slog"
"strings" "strings"
"aleesa-misc-go/internal/log"
) )
// MsgParser горутинка, которая парсит json-чики прилетевшие из REDIS-ки. // MsgParser горутинка, которая парсит json-чики прилетевшие из REDIS-ки.
@@ -15,29 +15,29 @@ func MsgParser(ctx context.Context, msg string) {
j rMsg j rMsg
) )
slog.Debug(fmt.Sprintf("Incomming raw json: %s", msg)) log.Debugf("Incomming raw json: %s", msg)
if err := json.Unmarshal([]byte(msg), &j); err != nil { if err := json.Unmarshal([]byte(msg), &j); err != nil {
slog.Warn(fmt.Sprintf("Unable to to parse message from redis channel: %s", err)) log.Warnf("Unable to to parse message from redis channel: %s", err)
return return
} }
// Validate our j. // Validate our j.
if exist := j.From; exist == "" { if exist := j.From; exist == "" {
slog.Warn(fmt.Sprintf("Incorrect msg from redis, no from field: %s", msg)) log.Warnf("Incorrect msg from redis, no from field: %s", msg)
return return
} }
if exist := j.Chatid; exist == "" { if exist := j.Chatid; exist == "" {
slog.Warn(fmt.Sprintf("Incorrect msg from redis, no chatid field: %s", msg)) log.Warnf("Incorrect msg from redis, no chatid field: %s", msg)
return return
} }
if exist := j.Userid; exist == "" { if exist := j.Userid; exist == "" {
slog.Warn(fmt.Sprintf("Incorrect msg from redis, no userid field: %s", msg)) log.Warnf("Incorrect msg from redis, no userid field: %s", msg)
return return
} }
@@ -45,19 +45,19 @@ func MsgParser(ctx context.Context, msg string) {
// j.Threadid может быть пустым, значит либо нам его не дали, либо дали пустым. Это нормально. // j.Threadid может быть пустым, значит либо нам его не дали, либо дали пустым. Это нормально.
if exist := j.Message; exist == "" { if exist := j.Message; exist == "" {
slog.Warn(fmt.Sprintf("Incorrect msg from redis, no message field: %s", msg)) log.Warnf("Incorrect msg from redis, no message field: %s", msg)
return return
} }
if exist := j.Plugin; exist == "" { if exist := j.Plugin; exist == "" {
slog.Warn(fmt.Sprintf("Incorrect msg from redis, no plugin field: %s", msg)) log.Warnf("Incorrect msg from redis, no plugin field: %s", msg)
return return
} }
if exist := j.Mode; exist == "" { if exist := j.Mode; exist == "" {
slog.Warn(fmt.Sprintf("Incorrect msg from redis, no mode field: %s", msg)) log.Warnf("Incorrect msg from redis, no mode field: %s", msg)
return return
} }
@@ -82,7 +82,7 @@ func MsgParser(ctx context.Context, msg string) {
// Если у нас циклическая пересылка сообщения, попробуем её тут разорвать, отбросив сообщение. // Если у нас циклическая пересылка сообщения, попробуем её тут разорвать, отбросив сообщение.
if j.Misc.Fwdcnt > Config.ForwardsMax { if j.Misc.Fwdcnt > Config.ForwardsMax {
slog.Warn(fmt.Sprintf("Discarding msg with fwd_cnt exceeding max value: %s", msg)) log.Warnf("Discarding msg with fwd_cnt exceeding max value: %s", msg)
return return
} }
@@ -222,16 +222,16 @@ func MsgParser(ctx context.Context, msg string) {
data, err := json.Marshal(message) data, err := json.Marshal(message)
if err != nil { if err != nil {
slog.Warn(fmt.Sprintf("Unable to to serialize message for redis: %s", err)) log.Warnf("Unable to to serialize message for redis: %s", err)
return return
} }
// Заталкиваем наш json в редиску. // Заталкиваем наш json в редиску.
if err := RedisClient.Publish(ctx, sendTo, data).Err(); err != nil { if err := RedisClient.Publish(ctx, sendTo, data).Err(); err != nil {
slog.Warn(fmt.Sprintf("Unable to send data to redis channel %s: %s", sendTo, err)) log.Warnf("Unable to send data to redis channel %s: %s", sendTo, err)
} else { } else {
slog.Debug(fmt.Sprintf("Send msg to redis channel %s: %s", sendTo, string(data))) log.Debugf("Send msg to redis channel %s: %s", sendTo, string(data))
} }
} }

View File

@@ -3,10 +3,11 @@ package misc
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"log/slog"
"os" "os"
"path/filepath" "path/filepath"
"aleesa-misc-go/internal/log"
"github.com/hjson/hjson-go" "github.com/hjson/hjson-go"
) )
@@ -17,7 +18,7 @@ func ReadConfig() {
executablePath, err := os.Executable() executablePath, err := os.Executable()
if err != nil { if err != nil {
slog.Error(fmt.Sprintf("Unable to get current executable path: %s", err)) log.Errorf("Unable to get current executable path: %s", err)
} }
configJSONPath := fmt.Sprintf("%s/data/config.json", filepath.Dir(executablePath)) configJSONPath := fmt.Sprintf("%s/data/config.json", filepath.Dir(executablePath))
@@ -39,7 +40,7 @@ func ReadConfig() {
// Конфиг-файл длинноват для конфига, попробуем следующего кандидата // Конфиг-файл длинноват для конфига, попробуем следующего кандидата
if fileInfo.Size() > 65535 { if fileInfo.Size() > 65535 {
slog.Warn(fmt.Sprintf("Config file %s is too long for config, skipping", location)) log.Warnf("Config file %s is too long for config, skipping", location)
continue continue
} }
@@ -48,7 +49,7 @@ func ReadConfig() {
// Не удалось прочитать, попробуем следующего кандидата // Не удалось прочитать, попробуем следующего кандидата
if err != nil { if err != nil {
slog.Warn(fmt.Sprintf("Skip reading config file %s: %s", location, err)) log.Warnf("Skip reading config file %s: %s", location, err)
continue continue
} }
@@ -65,7 +66,7 @@ func ReadConfig() {
// Не удалось распарсить - попробуем следующего кандидата // Не удалось распарсить - попробуем следующего кандидата
if err != nil { if err != nil {
slog.Warn(fmt.Sprintf("Skip parsing config file %s: %s", location, err)) log.Warnf("Skip parsing config file %s: %s", location, err)
continue continue
} }
@@ -74,13 +75,13 @@ func ReadConfig() {
// Не удалось преобразовать map-ку в json // Не удалось преобразовать map-ку в json
if err != nil { if err != nil {
slog.Warn(fmt.Sprintf("Skip parsing config file %s: %s", location, err)) log.Warnf("Skip parsing config file %s: %s", location, err)
continue continue
} }
if err := json.Unmarshal(tmpjson, &sampleConfig); err != nil { if err := json.Unmarshal(tmpjson, &sampleConfig); err != nil {
slog.Warn(fmt.Sprintf("Skip parsing config file %s: %s", location, err)) log.Warnf("Skip parsing config file %s: %s", location, err)
continue continue
} }
@@ -105,7 +106,7 @@ func ReadConfig() {
// sampleConfig.Log = "" if not set // sampleConfig.Log = "" if not set
if sampleConfig.Channel == "" { if sampleConfig.Channel == "" {
slog.Error(fmt.Sprintf("Channel field in config file %s must be set", location)) log.Errorf("Channel field in config file %s must be set", location)
} }
// Частичная проверка, ровно то, куда мы _точно_ щепрввляем сообщения исходя из бизнес-логики приложения // Частичная проверка, ровно то, куда мы _точно_ щепрввляем сообщения исходя из бизнес-логики приложения
@@ -130,7 +131,7 @@ func ReadConfig() {
} }
if sampleConfig.Csign == "" { if sampleConfig.Csign == "" {
slog.Error(fmt.Sprintf("Csign field in config file %s must be set", location)) log.Errorf("Csign field in config file %s must be set", location)
} }
if sampleConfig.ForwardsMax == 0 { if sampleConfig.ForwardsMax == 0 {
@@ -140,13 +141,13 @@ func ReadConfig() {
Config = sampleConfig Config = sampleConfig
configLoaded = true configLoaded = true
slog.Info(fmt.Sprintf("Using %s as config file", location)) log.Infof("Using %s as config file", location)
break break
} }
if !configLoaded { if !configLoaded {
slog.Error("Config was not loaded! Refusing to start.") log.Error("Config was not loaded! Refusing to start.")
os.Exit(1) os.Exit(1)
} }
} }

View File

@@ -1,27 +1,27 @@
package misc package misc
import ( import (
"fmt"
"log/slog"
"os" "os"
"syscall" "syscall"
"aleesa-misc-go/internal/log"
) )
// SigHandler хэндлер сигналов закрывает все бд и сваливает из приложения. // SigHandler хэндлер сигналов закрывает все бд и сваливает из приложения.
func SigHandler() { func SigHandler() {
var err error var err error
slog.Info("Install signal handler") log.Info("Install signal handler")
for { for {
var s = <-SigChan var s = <-SigChan
switch s { switch s {
case syscall.SIGINT: case syscall.SIGINT:
slog.Info("Got SIGINT, quitting") log.Info("Got SIGINT, quitting")
case syscall.SIGTERM: case syscall.SIGTERM:
slog.Info("Got SIGTERM, quitting") log.Info("Got SIGTERM, quitting")
case syscall.SIGQUIT: case syscall.SIGQUIT:
slog.Info("Got SIGQUIT, quitting") log.Info("Got SIGQUIT, quitting")
// Заходим на новую итерацию, если у нас "неинтересный" сигнал. // Заходим на новую итерацию, если у нас "неинтересный" сигнал.
default: default:
@@ -33,11 +33,11 @@ func SigHandler() {
// Отпишемся от всех каналов и закроем коннект к редиске. // Отпишемся от всех каналов и закроем коннект к редиске.
if err = Subscriber.Unsubscribe(Ctx); err != nil { if err = Subscriber.Unsubscribe(Ctx); err != nil {
slog.Error(fmt.Sprintf("Unable to unsubscribe from redis channels cleanly: %s", err)) log.Errorf("Unable to unsubscribe from redis channels cleanly: %s", err)
} }
if err = Subscriber.Close(); err != nil { if err = Subscriber.Close(); err != nil {
slog.Error(fmt.Sprintf("Unable to close redis connection cleanly: %s", err)) log.Errorf("Unable to close redis connection cleanly: %s", err)
} }
os.Exit(0) os.Exit(0)

View File

@@ -3,7 +3,8 @@
[![Go Reference](https://pkg.go.dev/badge/github.com/cespare/xxhash/v2.svg)](https://pkg.go.dev/github.com/cespare/xxhash/v2) [![Go Reference](https://pkg.go.dev/badge/github.com/cespare/xxhash/v2.svg)](https://pkg.go.dev/github.com/cespare/xxhash/v2)
[![Test](https://github.com/cespare/xxhash/actions/workflows/test.yml/badge.svg)](https://github.com/cespare/xxhash/actions/workflows/test.yml) [![Test](https://github.com/cespare/xxhash/actions/workflows/test.yml/badge.svg)](https://github.com/cespare/xxhash/actions/workflows/test.yml)
xxhash is a Go implementation of the 64-bit [xxHash] algorithm, XXH64. This is a xxhash is a Go implementation of the 64-bit
[xxHash](http://cyan4973.github.io/xxHash/) algorithm, XXH64. This is a
high-quality hashing algorithm that is much faster than anything in the Go high-quality hashing algorithm that is much faster than anything in the Go
standard library. standard library.
@@ -24,11 +25,8 @@ func (*Digest) WriteString(string) (int, error)
func (*Digest) Sum64() uint64 func (*Digest) Sum64() uint64
``` ```
The package is written with optimized pure Go and also contains even faster This implementation provides a fast pure-Go implementation and an even faster
assembly implementations for amd64 and arm64. If desired, the `purego` build tag assembly implementation for amd64.
opts into using the Go code even on those architectures.
[xxHash]: http://cyan4973.github.io/xxHash/
## Compatibility ## Compatibility
@@ -47,20 +45,19 @@ I recommend using the latest release of Go.
Here are some quick benchmarks comparing the pure-Go and assembly Here are some quick benchmarks comparing the pure-Go and assembly
implementations of Sum64. implementations of Sum64.
| input size | purego | asm | | input size | purego | asm |
| ---------- | --------- | --------- | | --- | --- | --- |
| 4 B | 1.3 GB/s | 1.2 GB/s | | 5 B | 979.66 MB/s | 1291.17 MB/s |
| 16 B | 2.9 GB/s | 3.5 GB/s | | 100 B | 7475.26 MB/s | 7973.40 MB/s |
| 100 B | 6.9 GB/s | 8.1 GB/s | | 4 KB | 17573.46 MB/s | 17602.65 MB/s |
| 4 KB | 11.7 GB/s | 16.7 GB/s | | 10 MB | 17131.46 MB/s | 17142.16 MB/s |
| 10 MB | 12.0 GB/s | 17.3 GB/s |
These numbers were generated on Ubuntu 20.04 with an Intel Xeon Platinum 8252C These numbers were generated on Ubuntu 18.04 with an Intel i7-8700K CPU using
CPU using the following commands under Go 1.19.2: the following commands under Go 1.11.2:
``` ```
benchstat <(go test -tags purego -benchtime 500ms -count 15 -bench 'Sum64$') $ go test -tags purego -benchtime 10s -bench '/xxhash,direct,bytes'
benchstat <(go test -benchtime 500ms -count 15 -bench 'Sum64$') $ go test -benchtime 10s -bench '/xxhash,direct,bytes'
``` ```
## Projects using this package ## Projects using this package
@@ -70,5 +67,3 @@ benchstat <(go test -benchtime 500ms -count 15 -bench 'Sum64$')
- [VictoriaMetrics](https://github.com/VictoriaMetrics/VictoriaMetrics) - [VictoriaMetrics](https://github.com/VictoriaMetrics/VictoriaMetrics)
- [FreeCache](https://github.com/coocood/freecache) - [FreeCache](https://github.com/coocood/freecache)
- [FastCache](https://github.com/VictoriaMetrics/fastcache) - [FastCache](https://github.com/VictoriaMetrics/fastcache)
- [Ristretto](https://github.com/dgraph-io/ristretto)
- [Badger](https://github.com/dgraph-io/badger)

View File

@@ -1,10 +0,0 @@
#!/bin/bash
set -eu -o pipefail
# Small convenience script for running the tests with various combinations of
# arch/tags. This assumes we're running on amd64 and have qemu available.
go test ./...
go test -tags purego ./...
GOARCH=arm64 go test
GOARCH=arm64 go test -tags purego

View File

@@ -16,16 +16,21 @@ const (
prime5 uint64 = 2870177450012600261 prime5 uint64 = 2870177450012600261
) )
// Store the primes in an array as well. // NOTE(caleb): I'm using both consts and vars of the primes. Using consts where
// // possible in the Go code is worth a small (but measurable) performance boost
// The consts are used when possible in Go code to avoid MOVs but we need a // by avoiding some MOVQs. Vars are needed for the asm and also are useful for
// contiguous array for the assembly code. // convenience in the Go code in a few places where we need to intentionally
var primes = [...]uint64{prime1, prime2, prime3, prime4, prime5} // avoid constant arithmetic (e.g., v1 := prime1 + prime2 fails because the
// result overflows a uint64).
var (
prime1v = prime1
prime2v = prime2
prime3v = prime3
prime4v = prime4
prime5v = prime5
)
// Digest implements hash.Hash64. // Digest implements hash.Hash64.
//
// Note that a zero-valued Digest is not ready to receive writes.
// Call Reset or create a Digest using New before calling other methods.
type Digest struct { type Digest struct {
v1 uint64 v1 uint64
v2 uint64 v2 uint64
@@ -36,31 +41,19 @@ type Digest struct {
n int // how much of mem is used n int // how much of mem is used
} }
// New creates a new Digest with a zero seed. // New creates a new Digest that computes the 64-bit xxHash algorithm.
func New() *Digest { func New() *Digest {
return NewWithSeed(0)
}
// NewWithSeed creates a new Digest with the given seed.
func NewWithSeed(seed uint64) *Digest {
var d Digest var d Digest
d.ResetWithSeed(seed) d.Reset()
return &d return &d
} }
// Reset clears the Digest's state so that it can be reused. // Reset clears the Digest's state so that it can be reused.
// It uses a seed value of zero.
func (d *Digest) Reset() { func (d *Digest) Reset() {
d.ResetWithSeed(0) d.v1 = prime1v + prime2
} d.v2 = prime2
d.v3 = 0
// ResetWithSeed clears the Digest's state so that it can be reused. d.v4 = -prime1v
// It uses the given seed to initialize the state.
func (d *Digest) ResetWithSeed(seed uint64) {
d.v1 = seed + prime1 + prime2
d.v2 = seed + prime2
d.v3 = seed
d.v4 = seed - prime1
d.total = 0 d.total = 0
d.n = 0 d.n = 0
} }
@@ -76,23 +69,21 @@ func (d *Digest) Write(b []byte) (n int, err error) {
n = len(b) n = len(b)
d.total += uint64(n) d.total += uint64(n)
memleft := d.mem[d.n&(len(d.mem)-1):]
if d.n+n < 32 { if d.n+n < 32 {
// This new data doesn't even fill the current block. // This new data doesn't even fill the current block.
copy(memleft, b) copy(d.mem[d.n:], b)
d.n += n d.n += n
return return
} }
if d.n > 0 { if d.n > 0 {
// Finish off the partial block. // Finish off the partial block.
c := copy(memleft, b) copy(d.mem[d.n:], b)
d.v1 = round(d.v1, u64(d.mem[0:8])) d.v1 = round(d.v1, u64(d.mem[0:8]))
d.v2 = round(d.v2, u64(d.mem[8:16])) d.v2 = round(d.v2, u64(d.mem[8:16]))
d.v3 = round(d.v3, u64(d.mem[16:24])) d.v3 = round(d.v3, u64(d.mem[16:24]))
d.v4 = round(d.v4, u64(d.mem[24:32])) d.v4 = round(d.v4, u64(d.mem[24:32]))
b = b[c:] b = b[32-d.n:]
d.n = 0 d.n = 0
} }
@@ -142,20 +133,21 @@ func (d *Digest) Sum64() uint64 {
h += d.total h += d.total
b := d.mem[:d.n&(len(d.mem)-1)] i, end := 0, d.n
for ; len(b) >= 8; b = b[8:] { for ; i+8 <= end; i += 8 {
k1 := round(0, u64(b[:8])) k1 := round(0, u64(d.mem[i:i+8]))
h ^= k1 h ^= k1
h = rol27(h)*prime1 + prime4 h = rol27(h)*prime1 + prime4
} }
if len(b) >= 4 { if i+4 <= end {
h ^= uint64(u32(b[:4])) * prime1 h ^= uint64(u32(d.mem[i:i+4])) * prime1
h = rol23(h)*prime2 + prime3 h = rol23(h)*prime2 + prime3
b = b[4:] i += 4
} }
for ; len(b) > 0; b = b[1:] { for i < end {
h ^= uint64(b[0]) * prime5 h ^= uint64(d.mem[i]) * prime5
h = rol11(h) * prime1 h = rol11(h) * prime1
i++
} }
h ^= h >> 33 h ^= h >> 33

View File

@@ -1,12 +1,10 @@
//go:build (amd64 || arm64) && !appengine && gc && !purego
// +build amd64 arm64
// +build !appengine // +build !appengine
// +build gc // +build gc
// +build !purego // +build !purego
package xxhash package xxhash
// Sum64 computes the 64-bit xxHash digest of b with a zero seed. // Sum64 computes the 64-bit xxHash digest of b.
// //
//go:noescape //go:noescape
func Sum64(b []byte) uint64 func Sum64(b []byte) uint64

View File

@@ -1,209 +1,215 @@
//go:build !appengine && gc && !purego
// +build !appengine // +build !appengine
// +build gc // +build gc
// +build !purego // +build !purego
#include "textflag.h" #include "textflag.h"
// Registers: // Register allocation:
#define h AX // AX h
#define d AX // SI pointer to advance through b
#define p SI // pointer to advance through b // DX n
#define n DX // BX loop end
#define end BX // loop end // R8 v1, k1
#define v1 R8 // R9 v2
#define v2 R9 // R10 v3
#define v3 R10 // R11 v4
#define v4 R11 // R12 tmp
#define x R12 // R13 prime1v
#define prime1 R13 // R14 prime2v
#define prime2 R14 // DI prime4v
#define prime4 DI
#define round(acc, x) \ // round reads from and advances the buffer pointer in SI.
IMULQ prime2, x \ // It assumes that R13 has prime1v and R14 has prime2v.
ADDQ x, acc \ #define round(r) \
ROLQ $31, acc \ MOVQ (SI), R12 \
IMULQ prime1, acc ADDQ $8, SI \
IMULQ R14, R12 \
ADDQ R12, r \
ROLQ $31, r \
IMULQ R13, r
// round0 performs the operation x = round(0, x). // mergeRound applies a merge round on the two registers acc and val.
#define round0(x) \ // It assumes that R13 has prime1v, R14 has prime2v, and DI has prime4v.
IMULQ prime2, x \ #define mergeRound(acc, val) \
ROLQ $31, x \ IMULQ R14, val \
IMULQ prime1, x ROLQ $31, val \
IMULQ R13, val \
// mergeRound applies a merge round on the two registers acc and x. XORQ val, acc \
// It assumes that prime1, prime2, and prime4 have been loaded. IMULQ R13, acc \
#define mergeRound(acc, x) \ ADDQ DI, acc
round0(x) \
XORQ x, acc \
IMULQ prime1, acc \
ADDQ prime4, acc
// blockLoop processes as many 32-byte blocks as possible,
// updating v1, v2, v3, and v4. It assumes that there is at least one block
// to process.
#define blockLoop() \
loop: \
MOVQ +0(p), x \
round(v1, x) \
MOVQ +8(p), x \
round(v2, x) \
MOVQ +16(p), x \
round(v3, x) \
MOVQ +24(p), x \
round(v4, x) \
ADDQ $32, p \
CMPQ p, end \
JLE loop
// func Sum64(b []byte) uint64 // func Sum64(b []byte) uint64
TEXT ·Sum64(SB), NOSPLIT|NOFRAME, $0-32 TEXT ·Sum64(SB), NOSPLIT, $0-32
// Load fixed primes. // Load fixed primes.
MOVQ ·primes+0(SB), prime1 MOVQ ·prime1v(SB), R13
MOVQ ·primes+8(SB), prime2 MOVQ ·prime2v(SB), R14
MOVQ ·primes+24(SB), prime4 MOVQ ·prime4v(SB), DI
// Load slice. // Load slice.
MOVQ b_base+0(FP), p MOVQ b_base+0(FP), SI
MOVQ b_len+8(FP), n MOVQ b_len+8(FP), DX
LEAQ (p)(n*1), end LEAQ (SI)(DX*1), BX
// The first loop limit will be len(b)-32. // The first loop limit will be len(b)-32.
SUBQ $32, end SUBQ $32, BX
// Check whether we have at least one block. // Check whether we have at least one block.
CMPQ n, $32 CMPQ DX, $32
JLT noBlocks JLT noBlocks
// Set up initial state (v1, v2, v3, v4). // Set up initial state (v1, v2, v3, v4).
MOVQ prime1, v1 MOVQ R13, R8
ADDQ prime2, v1 ADDQ R14, R8
MOVQ prime2, v2 MOVQ R14, R9
XORQ v3, v3 XORQ R10, R10
XORQ v4, v4 XORQ R11, R11
SUBQ prime1, v4 SUBQ R13, R11
blockLoop() // Loop until SI > BX.
blockLoop:
round(R8)
round(R9)
round(R10)
round(R11)
MOVQ v1, h CMPQ SI, BX
ROLQ $1, h JLE blockLoop
MOVQ v2, x
ROLQ $7, x
ADDQ x, h
MOVQ v3, x
ROLQ $12, x
ADDQ x, h
MOVQ v4, x
ROLQ $18, x
ADDQ x, h
mergeRound(h, v1) MOVQ R8, AX
mergeRound(h, v2) ROLQ $1, AX
mergeRound(h, v3) MOVQ R9, R12
mergeRound(h, v4) ROLQ $7, R12
ADDQ R12, AX
MOVQ R10, R12
ROLQ $12, R12
ADDQ R12, AX
MOVQ R11, R12
ROLQ $18, R12
ADDQ R12, AX
mergeRound(AX, R8)
mergeRound(AX, R9)
mergeRound(AX, R10)
mergeRound(AX, R11)
JMP afterBlocks JMP afterBlocks
noBlocks: noBlocks:
MOVQ ·primes+32(SB), h MOVQ ·prime5v(SB), AX
afterBlocks: afterBlocks:
ADDQ n, h ADDQ DX, AX
ADDQ $24, end // Right now BX has len(b)-32, and we want to loop until SI > len(b)-8.
CMPQ p, end ADDQ $24, BX
JG try4
loop8: CMPQ SI, BX
MOVQ (p), x JG fourByte
ADDQ $8, p
round0(x)
XORQ x, h
ROLQ $27, h
IMULQ prime1, h
ADDQ prime4, h
CMPQ p, end wordLoop:
JLE loop8 // Calculate k1.
MOVQ (SI), R8
ADDQ $8, SI
IMULQ R14, R8
ROLQ $31, R8
IMULQ R13, R8
try4: XORQ R8, AX
ADDQ $4, end ROLQ $27, AX
CMPQ p, end IMULQ R13, AX
JG try1 ADDQ DI, AX
MOVL (p), x CMPQ SI, BX
ADDQ $4, p JLE wordLoop
IMULQ prime1, x
XORQ x, h
ROLQ $23, h fourByte:
IMULQ prime2, h ADDQ $4, BX
ADDQ ·primes+16(SB), h CMPQ SI, BX
JG singles
try1: MOVL (SI), R8
ADDQ $4, end ADDQ $4, SI
CMPQ p, end IMULQ R13, R8
XORQ R8, AX
ROLQ $23, AX
IMULQ R14, AX
ADDQ ·prime3v(SB), AX
singles:
ADDQ $4, BX
CMPQ SI, BX
JGE finalize JGE finalize
loop1: singlesLoop:
MOVBQZX (p), x MOVBQZX (SI), R12
ADDQ $1, p ADDQ $1, SI
IMULQ ·primes+32(SB), x IMULQ ·prime5v(SB), R12
XORQ x, h XORQ R12, AX
ROLQ $11, h
IMULQ prime1, h
CMPQ p, end ROLQ $11, AX
JL loop1 IMULQ R13, AX
CMPQ SI, BX
JL singlesLoop
finalize: finalize:
MOVQ h, x MOVQ AX, R12
SHRQ $33, x SHRQ $33, R12
XORQ x, h XORQ R12, AX
IMULQ prime2, h IMULQ R14, AX
MOVQ h, x MOVQ AX, R12
SHRQ $29, x SHRQ $29, R12
XORQ x, h XORQ R12, AX
IMULQ ·primes+16(SB), h IMULQ ·prime3v(SB), AX
MOVQ h, x MOVQ AX, R12
SHRQ $32, x SHRQ $32, R12
XORQ x, h XORQ R12, AX
MOVQ h, ret+24(FP) MOVQ AX, ret+24(FP)
RET RET
// writeBlocks uses the same registers as above except that it uses AX to store
// the d pointer.
// func writeBlocks(d *Digest, b []byte) int // func writeBlocks(d *Digest, b []byte) int
TEXT ·writeBlocks(SB), NOSPLIT|NOFRAME, $0-40 TEXT ·writeBlocks(SB), NOSPLIT, $0-40
// Load fixed primes needed for round. // Load fixed primes needed for round.
MOVQ ·primes+0(SB), prime1 MOVQ ·prime1v(SB), R13
MOVQ ·primes+8(SB), prime2 MOVQ ·prime2v(SB), R14
// Load slice. // Load slice.
MOVQ b_base+8(FP), p MOVQ b_base+8(FP), SI
MOVQ b_len+16(FP), n MOVQ b_len+16(FP), DX
LEAQ (p)(n*1), end LEAQ (SI)(DX*1), BX
SUBQ $32, end SUBQ $32, BX
// Load vN from d. // Load vN from d.
MOVQ s+0(FP), d MOVQ d+0(FP), AX
MOVQ 0(d), v1 MOVQ 0(AX), R8 // v1
MOVQ 8(d), v2 MOVQ 8(AX), R9 // v2
MOVQ 16(d), v3 MOVQ 16(AX), R10 // v3
MOVQ 24(d), v4 MOVQ 24(AX), R11 // v4
// We don't need to check the loop condition here; this function is // We don't need to check the loop condition here; this function is
// always called with at least one block of data to process. // always called with at least one block of data to process.
blockLoop() blockLoop:
round(R8)
round(R9)
round(R10)
round(R11)
CMPQ SI, BX
JLE blockLoop
// Copy vN back to d. // Copy vN back to d.
MOVQ v1, 0(d) MOVQ R8, 0(AX)
MOVQ v2, 8(d) MOVQ R9, 8(AX)
MOVQ v3, 16(d) MOVQ R10, 16(AX)
MOVQ v4, 24(d) MOVQ R11, 24(AX)
// The number of bytes written is p minus the old base pointer. // The number of bytes written is SI minus the old base pointer.
SUBQ b_base+8(FP), p SUBQ b_base+8(FP), SI
MOVQ p, ret+32(FP) MOVQ SI, ret+32(FP)
RET RET

View File

@@ -1,183 +0,0 @@
//go:build !appengine && gc && !purego
// +build !appengine
// +build gc
// +build !purego
#include "textflag.h"
// Registers:
#define digest R1
#define h R2 // return value
#define p R3 // input pointer
#define n R4 // input length
#define nblocks R5 // n / 32
#define prime1 R7
#define prime2 R8
#define prime3 R9
#define prime4 R10
#define prime5 R11
#define v1 R12
#define v2 R13
#define v3 R14
#define v4 R15
#define x1 R20
#define x2 R21
#define x3 R22
#define x4 R23
#define round(acc, x) \
MADD prime2, acc, x, acc \
ROR $64-31, acc \
MUL prime1, acc
// round0 performs the operation x = round(0, x).
#define round0(x) \
MUL prime2, x \
ROR $64-31, x \
MUL prime1, x
#define mergeRound(acc, x) \
round0(x) \
EOR x, acc \
MADD acc, prime4, prime1, acc
// blockLoop processes as many 32-byte blocks as possible,
// updating v1, v2, v3, and v4. It assumes that n >= 32.
#define blockLoop() \
LSR $5, n, nblocks \
PCALIGN $16 \
loop: \
LDP.P 16(p), (x1, x2) \
LDP.P 16(p), (x3, x4) \
round(v1, x1) \
round(v2, x2) \
round(v3, x3) \
round(v4, x4) \
SUB $1, nblocks \
CBNZ nblocks, loop
// func Sum64(b []byte) uint64
TEXT ·Sum64(SB), NOSPLIT|NOFRAME, $0-32
LDP b_base+0(FP), (p, n)
LDP ·primes+0(SB), (prime1, prime2)
LDP ·primes+16(SB), (prime3, prime4)
MOVD ·primes+32(SB), prime5
CMP $32, n
CSEL LT, prime5, ZR, h // if n < 32 { h = prime5 } else { h = 0 }
BLT afterLoop
ADD prime1, prime2, v1
MOVD prime2, v2
MOVD $0, v3
NEG prime1, v4
blockLoop()
ROR $64-1, v1, x1
ROR $64-7, v2, x2
ADD x1, x2
ROR $64-12, v3, x3
ROR $64-18, v4, x4
ADD x3, x4
ADD x2, x4, h
mergeRound(h, v1)
mergeRound(h, v2)
mergeRound(h, v3)
mergeRound(h, v4)
afterLoop:
ADD n, h
TBZ $4, n, try8
LDP.P 16(p), (x1, x2)
round0(x1)
// NOTE: here and below, sequencing the EOR after the ROR (using a
// rotated register) is worth a small but measurable speedup for small
// inputs.
ROR $64-27, h
EOR x1 @> 64-27, h, h
MADD h, prime4, prime1, h
round0(x2)
ROR $64-27, h
EOR x2 @> 64-27, h, h
MADD h, prime4, prime1, h
try8:
TBZ $3, n, try4
MOVD.P 8(p), x1
round0(x1)
ROR $64-27, h
EOR x1 @> 64-27, h, h
MADD h, prime4, prime1, h
try4:
TBZ $2, n, try2
MOVWU.P 4(p), x2
MUL prime1, x2
ROR $64-23, h
EOR x2 @> 64-23, h, h
MADD h, prime3, prime2, h
try2:
TBZ $1, n, try1
MOVHU.P 2(p), x3
AND $255, x3, x1
LSR $8, x3, x2
MUL prime5, x1
ROR $64-11, h
EOR x1 @> 64-11, h, h
MUL prime1, h
MUL prime5, x2
ROR $64-11, h
EOR x2 @> 64-11, h, h
MUL prime1, h
try1:
TBZ $0, n, finalize
MOVBU (p), x4
MUL prime5, x4
ROR $64-11, h
EOR x4 @> 64-11, h, h
MUL prime1, h
finalize:
EOR h >> 33, h
MUL prime2, h
EOR h >> 29, h
MUL prime3, h
EOR h >> 32, h
MOVD h, ret+24(FP)
RET
// func writeBlocks(d *Digest, b []byte) int
TEXT ·writeBlocks(SB), NOSPLIT|NOFRAME, $0-40
LDP ·primes+0(SB), (prime1, prime2)
// Load state. Assume v[1-4] are stored contiguously.
MOVD d+0(FP), digest
LDP 0(digest), (v1, v2)
LDP 16(digest), (v3, v4)
LDP b_base+8(FP), (p, n)
blockLoop()
// Store updated state.
STP (v1, v2), 0(digest)
STP (v3, v4), 16(digest)
BIC $31, n
MOVD n, ret+32(FP)
RET

View File

@@ -1,9 +1,8 @@
//go:build (!amd64 && !arm64) || appengine || !gc || purego // +build !amd64 appengine !gc purego
// +build !amd64,!arm64 appengine !gc purego
package xxhash package xxhash
// Sum64 computes the 64-bit xxHash digest of b with a zero seed. // Sum64 computes the 64-bit xxHash digest of b.
func Sum64(b []byte) uint64 { func Sum64(b []byte) uint64 {
// A simpler version would be // A simpler version would be
// d := New() // d := New()
@@ -15,10 +14,10 @@ func Sum64(b []byte) uint64 {
var h uint64 var h uint64
if n >= 32 { if n >= 32 {
v1 := primes[0] + prime2 v1 := prime1v + prime2
v2 := prime2 v2 := prime2
v3 := uint64(0) v3 := uint64(0)
v4 := -primes[0] v4 := -prime1v
for len(b) >= 32 { for len(b) >= 32 {
v1 = round(v1, u64(b[0:8:len(b)])) v1 = round(v1, u64(b[0:8:len(b)]))
v2 = round(v2, u64(b[8:16:len(b)])) v2 = round(v2, u64(b[8:16:len(b)]))
@@ -37,18 +36,19 @@ func Sum64(b []byte) uint64 {
h += uint64(n) h += uint64(n)
for ; len(b) >= 8; b = b[8:] { i, end := 0, len(b)
k1 := round(0, u64(b[:8])) for ; i+8 <= end; i += 8 {
k1 := round(0, u64(b[i:i+8:len(b)]))
h ^= k1 h ^= k1
h = rol27(h)*prime1 + prime4 h = rol27(h)*prime1 + prime4
} }
if len(b) >= 4 { if i+4 <= end {
h ^= uint64(u32(b[:4])) * prime1 h ^= uint64(u32(b[i:i+4:len(b)])) * prime1
h = rol23(h)*prime2 + prime3 h = rol23(h)*prime2 + prime3
b = b[4:] i += 4
} }
for ; len(b) > 0; b = b[1:] { for ; i < end; i++ {
h ^= uint64(b[0]) * prime5 h ^= uint64(b[i]) * prime5
h = rol11(h) * prime1 h = rol11(h) * prime1
} }

View File

@@ -1,11 +1,10 @@
//go:build appengine
// +build appengine // +build appengine
// This file contains the safe implementations of otherwise unsafe-using code. // This file contains the safe implementations of otherwise unsafe-using code.
package xxhash package xxhash
// Sum64String computes the 64-bit xxHash digest of s with a zero seed. // Sum64String computes the 64-bit xxHash digest of s.
func Sum64String(s string) uint64 { func Sum64String(s string) uint64 {
return Sum64([]byte(s)) return Sum64([]byte(s))
} }

View File

@@ -1,4 +1,3 @@
//go:build !appengine
// +build !appengine // +build !appengine
// This file encapsulates usage of unsafe. // This file encapsulates usage of unsafe.
@@ -12,7 +11,7 @@ import (
// In the future it's possible that compiler optimizations will make these // In the future it's possible that compiler optimizations will make these
// XxxString functions unnecessary by realizing that calls such as // XxxString functions unnecessary by realizing that calls such as
// Sum64([]byte(s)) don't need to copy s. See https://go.dev/issue/2205. // Sum64([]byte(s)) don't need to copy s. See https://golang.org/issue/2205.
// If that happens, even if we keep these functions they can be replaced with // If that happens, even if we keep these functions they can be replaced with
// the trivial safe code. // the trivial safe code.
@@ -33,7 +32,7 @@ import (
// //
// See https://github.com/golang/go/issues/42739 for discussion. // See https://github.com/golang/go/issues/42739 for discussion.
// Sum64String computes the 64-bit xxHash digest of s with a zero seed. // Sum64String computes the 64-bit xxHash digest of s.
// It may be faster than Sum64([]byte(s)) by avoiding a copy. // It may be faster than Sum64([]byte(s)) by avoiding a copy.
func Sum64String(s string) uint64 { func Sum64String(s string) uint64 {
b := *(*[]byte)(unsafe.Pointer(&sliceHeader{s, len(s)})) b := *(*[]byte)(unsafe.Pointer(&sliceHeader{s, len(s)}))

4
vendor/modules.txt vendored
View File

@@ -1,4 +1,4 @@
# github.com/cespare/xxhash/v2 v2.3.0 # github.com/cespare/xxhash/v2 v2.1.2
## explicit; go 1.11 ## explicit; go 1.11
github.com/cespare/xxhash/v2 github.com/cespare/xxhash/v2
# github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f # github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f
@@ -17,5 +17,3 @@ github.com/go-redis/redis/v8/internal/util
# github.com/hjson/hjson-go v3.3.0+incompatible # github.com/hjson/hjson-go v3.3.0+incompatible
## explicit ## explicit
github.com/hjson/hjson-go github.com/hjson/hjson-go
# golang.org/x/sys v0.35.0
## explicit; go 1.23.0