diff --git a/cmd/syncthing/main.go b/cmd/syncthing/main.go
index b25d18ce..4b225946 100644
--- a/cmd/syncthing/main.go
+++ b/cmd/syncthing/main.go
@@ -46,6 +46,7 @@ import (
"github.com/syncthing/syncthing/lib/symlinks"
"github.com/syncthing/syncthing/lib/tlsutil"
"github.com/syncthing/syncthing/lib/upgrade"
+ "github.com/syncthing/syncthing/lib/weakhash"
"github.com/thejerf/suture"
@@ -623,8 +624,10 @@ func syncthingMain(runtimeOptions RuntimeOptions) {
sha256.SelectAlgo()
sha256.Report()
- perf := cpuBench(3, 150*time.Millisecond)
- l.Infof("Actual hashing performance is %.02f MB/s", perf)
+ perfWithWeakHash := cpuBench(3, 150*time.Millisecond, true)
+ l.Infof("Hashing performance with weak hash is %.02f MB/s", perfWithWeakHash)
+ perfWithoutWeakHash := cpuBench(3, 150*time.Millisecond, false)
+ l.Infof("Hashing performance without weak hash is %.02f MB/s", perfWithoutWeakHash)
// Emit the Starting event, now that we know who we are.
@@ -680,6 +683,22 @@ func syncthingMain(runtimeOptions RuntimeOptions) {
symlinks.Supported = false
}
+ if opts.WeakHashSelectionMethod == config.WeakHashAuto {
+ if perfWithoutWeakHash*0.8 > perfWithWeakHash {
+ l.Infof("Weak hash disabled, as it has an unacceptable performance impact.")
+ weakhash.Enabled = false
+ } else {
+ l.Infof("Weak hash enabled, as it has an acceptable performance impact.")
+ weakhash.Enabled = true
+ }
+ } else if opts.WeakHashSelectionMethod == config.WeakHashNever {
+ l.Infof("Disabling weak hash")
+ weakhash.Enabled = false
+ } else if opts.WeakHashSelectionMethod == config.WeakHashAlways {
+ l.Infof("Enabling weak hash")
+ weakhash.Enabled = true
+ }
+
if (opts.MaxRecvKbps > 0 || opts.MaxSendKbps > 0) && !opts.LimitBandwidthInLan {
lans, _ = osutil.GetLans()
for _, lan := range opts.AlwaysLocalNets {
diff --git a/cmd/syncthing/mocked_config_test.go b/cmd/syncthing/mocked_config_test.go
index 0ebdf9cf..66d65dbc 100644
--- a/cmd/syncthing/mocked_config_test.go
+++ b/cmd/syncthing/mocked_config_test.go
@@ -9,6 +9,7 @@ package main
import (
"github.com/syncthing/syncthing/lib/config"
"github.com/syncthing/syncthing/lib/protocol"
+ "github.com/syncthing/syncthing/lib/util"
)
type mockedConfig struct {
@@ -24,7 +25,9 @@ func (c *mockedConfig) ListenAddresses() []string {
}
func (c *mockedConfig) RawCopy() config.Configuration {
- return config.Configuration{}
+ cfg := config.Configuration{}
+ util.SetDefaults(&cfg.Options)
+ return cfg
}
func (c *mockedConfig) Options() config.OptionsConfiguration {
diff --git a/cmd/syncthing/usage_report.go b/cmd/syncthing/usage_report.go
index c304177f..08d212c3 100644
--- a/cmd/syncthing/usage_report.go
+++ b/cmd/syncthing/usage_report.go
@@ -113,7 +113,8 @@ func reportData(cfg configIntf, m modelIntf) map[string]interface{} {
var mem runtime.MemStats
runtime.ReadMemStats(&mem)
res["memoryUsageMiB"] = (mem.Sys - mem.HeapReleased) / 1024 / 1024
- res["sha256Perf"] = cpuBench(5, 125*time.Millisecond)
+ res["sha256Perf"] = cpuBench(5, 125*time.Millisecond, false)
+ res["hashPerf"] = cpuBench(5, 125*time.Millisecond, true)
bytes, err := memorySize()
if err == nil {
@@ -286,10 +287,10 @@ func (s *usageReportingService) Stop() {
}
// cpuBench returns CPU performance as a measure of single threaded SHA-256 MiB/s
-func cpuBench(iterations int, duration time.Duration) float64 {
+func cpuBench(iterations int, duration time.Duration, useWeakHash bool) float64 {
var perf float64
for i := 0; i < iterations; i++ {
- if v := cpuBenchOnce(duration); v > perf {
+ if v := cpuBenchOnce(duration, useWeakHash); v > perf {
perf = v
}
}
@@ -299,7 +300,7 @@ func cpuBench(iterations int, duration time.Duration) float64 {
var blocksResult []protocol.BlockInfo // so the result is not optimized away
-func cpuBenchOnce(duration time.Duration) float64 {
+func cpuBenchOnce(duration time.Duration, useWeakHash bool) float64 {
dataSize := 16 * protocol.BlockSize
bs := make([]byte, dataSize)
rand.Reader.Read(bs)
@@ -308,7 +309,7 @@ func cpuBenchOnce(duration time.Duration) float64 {
b := 0
for time.Since(t0) < duration {
r := bytes.NewReader(bs)
- blocksResult, _ = scanner.Blocks(r, protocol.BlockSize, int64(dataSize), nil, true)
+ blocksResult, _ = scanner.Blocks(r, protocol.BlockSize, int64(dataSize), nil, useWeakHash)
b += dataSize
}
d := time.Since(t0)
diff --git a/lib/config/config.go b/lib/config/config.go
index 722bb37d..fc56db76 100644
--- a/lib/config/config.go
+++ b/lib/config/config.go
@@ -332,6 +332,19 @@ func convertV17V18(cfg *Configuration) {
cfg.Version = 18
}
+func convertV16V17(cfg *Configuration) {
+ for i := range cfg.Folders {
+ cfg.Folders[i].Fsync = true
+ }
+
+ cfg.Version = 17
+}
+
+func convertV15V16(cfg *Configuration) {
+ // Triggers a database tweak
+ cfg.Version = 16
+}
+
func convertV14V15(cfg *Configuration) {
// Undo v0.13.0 broken migration
@@ -347,19 +360,6 @@ func convertV14V15(cfg *Configuration) {
cfg.Version = 15
}
-func convertV15V16(cfg *Configuration) {
- // Triggers a database tweak
- cfg.Version = 16
-}
-
-func convertV16V17(cfg *Configuration) {
- for i := range cfg.Folders {
- cfg.Folders[i].Fsync = true
- }
-
- cfg.Version = 17
-}
-
func convertV13V14(cfg *Configuration) {
// Not using the ignore cache is the new default. Disable it on existing
// configurations.
diff --git a/lib/config/config_test.go b/lib/config/config_test.go
index 9c3f5644..e7a60ded 100644
--- a/lib/config/config_test.go
+++ b/lib/config/config_test.go
@@ -65,6 +65,7 @@ func TestDefaultValues(t *testing.T) {
OverwriteRemoteDevNames: false,
TempIndexMinBlocks: 10,
UnackedNotificationIDs: []string{},
+ WeakHashSelectionMethod: WeakHashAuto,
}
cfg := New(device1)
@@ -203,6 +204,7 @@ func TestOverriddenValues(t *testing.T) {
UnackedNotificationIDs: []string{
"channelNotification", // added in 17->18 migration
},
+ WeakHashSelectionMethod: WeakHashNever,
}
os.Unsetenv("STNOUPGRADE")
diff --git a/lib/config/optionsconfiguration.go b/lib/config/optionsconfiguration.go
index 91a485eb..2663570a 100644
--- a/lib/config/optionsconfiguration.go
+++ b/lib/config/optionsconfiguration.go
@@ -6,43 +6,132 @@
package config
+import (
+ "encoding/json"
+ "encoding/xml"
+ "fmt"
+)
+
+type WeakHashSelectionMethod int
+
+const (
+ WeakHashAuto WeakHashSelectionMethod = iota
+ WeakHashAlways
+ WeakHashNever
+)
+
+func (m WeakHashSelectionMethod) MarshalString() (string, error) {
+ switch m {
+ case WeakHashAuto:
+ return "auto", nil
+ case WeakHashAlways:
+ return "always", nil
+ case WeakHashNever:
+ return "never", nil
+ default:
+ return "", fmt.Errorf("unrecognized hash selection method")
+ }
+}
+
+func (m WeakHashSelectionMethod) String() string {
+ s, err := m.MarshalString()
+ if err != nil {
+ panic(err)
+ }
+ return s
+}
+
+func (m *WeakHashSelectionMethod) UnmarshalString(value string) error {
+ switch value {
+ case "auto":
+ *m = WeakHashAuto
+ return nil
+ case "always":
+ *m = WeakHashAlways
+ return nil
+ case "never":
+ *m = WeakHashNever
+ return nil
+ }
+ return fmt.Errorf("unrecognized hash selection method")
+}
+
+func (m WeakHashSelectionMethod) MarshalJSON() ([]byte, error) {
+ val, err := m.MarshalString()
+ if err != nil {
+ return nil, err
+ }
+ return json.Marshal(val)
+}
+
+func (m *WeakHashSelectionMethod) UnmarshalJSON(data []byte) error {
+ var value string
+ if err := json.Unmarshal(data, &value); err != nil {
+ return err
+ }
+ return m.UnmarshalString(value)
+}
+
+func (m WeakHashSelectionMethod) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
+ val, err := m.MarshalString()
+ if err != nil {
+ return err
+ }
+ return e.EncodeElement(val, start)
+}
+
+func (m *WeakHashSelectionMethod) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
+ var value string
+ if err := d.DecodeElement(&value, &start); err != nil {
+ return err
+ }
+ return m.UnmarshalString(value)
+}
+
+func (WeakHashSelectionMethod) ParseDefault(value string) (interface{}, error) {
+ var m WeakHashSelectionMethod
+ err := m.UnmarshalString(value)
+ return m, err
+}
+
type OptionsConfiguration struct {
- ListenAddresses []string `xml:"listenAddress" json:"listenAddresses" default:"default"`
- GlobalAnnServers []string `xml:"globalAnnounceServer" json:"globalAnnounceServers" json:"globalAnnounceServer" default:"default"`
- GlobalAnnEnabled bool `xml:"globalAnnounceEnabled" json:"globalAnnounceEnabled" default:"true"`
- LocalAnnEnabled bool `xml:"localAnnounceEnabled" json:"localAnnounceEnabled" default:"true"`
- LocalAnnPort int `xml:"localAnnouncePort" json:"localAnnouncePort" default:"21027"`
- LocalAnnMCAddr string `xml:"localAnnounceMCAddr" json:"localAnnounceMCAddr" default:"[ff12::8384]:21027"`
- MaxSendKbps int `xml:"maxSendKbps" json:"maxSendKbps"`
- MaxRecvKbps int `xml:"maxRecvKbps" json:"maxRecvKbps"`
- ReconnectIntervalS int `xml:"reconnectionIntervalS" json:"reconnectionIntervalS" default:"60"`
- RelaysEnabled bool `xml:"relaysEnabled" json:"relaysEnabled" default:"true"`
- RelayReconnectIntervalM int `xml:"relayReconnectIntervalM" json:"relayReconnectIntervalM" default:"10"`
- StartBrowser bool `xml:"startBrowser" json:"startBrowser" default:"true"`
- NATEnabled bool `xml:"natEnabled" json:"natEnabled" default:"true"`
- NATLeaseM int `xml:"natLeaseMinutes" json:"natLeaseMinutes" default:"60"`
- NATRenewalM int `xml:"natRenewalMinutes" json:"natRenewalMinutes" default:"30"`
- NATTimeoutS int `xml:"natTimeoutSeconds" json:"natTimeoutSeconds" default:"10"`
- URAccepted int `xml:"urAccepted" json:"urAccepted"` // Accepted usage reporting version; 0 for off (undecided), -1 for off (permanently)
- URUniqueID string `xml:"urUniqueID" json:"urUniqueId"` // Unique ID for reporting purposes, regenerated when UR is turned on.
- URURL string `xml:"urURL" json:"urURL" default:"https://data.syncthing.net/newdata"`
- URPostInsecurely bool `xml:"urPostInsecurely" json:"urPostInsecurely" default:"false"` // For testing
- URInitialDelayS int `xml:"urInitialDelayS" json:"urInitialDelayS" default:"1800"`
- RestartOnWakeup bool `xml:"restartOnWakeup" json:"restartOnWakeup" default:"true"`
- AutoUpgradeIntervalH int `xml:"autoUpgradeIntervalH" json:"autoUpgradeIntervalH" default:"12"` // 0 for off
- UpgradeToPreReleases bool `xml:"upgradeToPreReleases" json:"upgradeToPreReleases"` // when auto upgrades are enabled
- KeepTemporariesH int `xml:"keepTemporariesH" json:"keepTemporariesH" default:"24"` // 0 for off
- CacheIgnoredFiles bool `xml:"cacheIgnoredFiles" json:"cacheIgnoredFiles" default:"false"`
- ProgressUpdateIntervalS int `xml:"progressUpdateIntervalS" json:"progressUpdateIntervalS" default:"5"`
- SymlinksEnabled bool `xml:"symlinksEnabled" json:"symlinksEnabled" default:"true"`
- LimitBandwidthInLan bool `xml:"limitBandwidthInLan" json:"limitBandwidthInLan" default:"false"`
- MinHomeDiskFreePct float64 `xml:"minHomeDiskFreePct" json:"minHomeDiskFreePct" default:"1"`
- ReleasesURL string `xml:"releasesURL" json:"releasesURL" default:"https://upgrades.syncthing.net/meta.json"`
- AlwaysLocalNets []string `xml:"alwaysLocalNet" json:"alwaysLocalNets"`
- OverwriteRemoteDevNames bool `xml:"overwriteRemoteDeviceNamesOnConnect" json:"overwriteRemoteDeviceNamesOnConnect" default:"false"`
- TempIndexMinBlocks int `xml:"tempIndexMinBlocks" json:"tempIndexMinBlocks" default:"10"`
- UnackedNotificationIDs []string `xml:"unackedNotificationID" json:"unackedNotificationIDs"`
- TrafficClass int `xml:"trafficClass" json:"trafficClass"`
+ ListenAddresses []string `xml:"listenAddress" json:"listenAddresses" default:"default"`
+ GlobalAnnServers []string `xml:"globalAnnounceServer" json:"globalAnnounceServers" json:"globalAnnounceServer" default:"default"`
+ GlobalAnnEnabled bool `xml:"globalAnnounceEnabled" json:"globalAnnounceEnabled" default:"true"`
+ LocalAnnEnabled bool `xml:"localAnnounceEnabled" json:"localAnnounceEnabled" default:"true"`
+ LocalAnnPort int `xml:"localAnnouncePort" json:"localAnnouncePort" default:"21027"`
+ LocalAnnMCAddr string `xml:"localAnnounceMCAddr" json:"localAnnounceMCAddr" default:"[ff12::8384]:21027"`
+ MaxSendKbps int `xml:"maxSendKbps" json:"maxSendKbps"`
+ MaxRecvKbps int `xml:"maxRecvKbps" json:"maxRecvKbps"`
+ ReconnectIntervalS int `xml:"reconnectionIntervalS" json:"reconnectionIntervalS" default:"60"`
+ RelaysEnabled bool `xml:"relaysEnabled" json:"relaysEnabled" default:"true"`
+ RelayReconnectIntervalM int `xml:"relayReconnectIntervalM" json:"relayReconnectIntervalM" default:"10"`
+ StartBrowser bool `xml:"startBrowser" json:"startBrowser" default:"true"`
+ NATEnabled bool `xml:"natEnabled" json:"natEnabled" default:"true"`
+ NATLeaseM int `xml:"natLeaseMinutes" json:"natLeaseMinutes" default:"60"`
+ NATRenewalM int `xml:"natRenewalMinutes" json:"natRenewalMinutes" default:"30"`
+ NATTimeoutS int `xml:"natTimeoutSeconds" json:"natTimeoutSeconds" default:"10"`
+ URAccepted int `xml:"urAccepted" json:"urAccepted"` // Accepted usage reporting version; 0 for off (undecided), -1 for off (permanently)
+ URUniqueID string `xml:"urUniqueID" json:"urUniqueId"` // Unique ID for reporting purposes, regenerated when UR is turned on.
+ URURL string `xml:"urURL" json:"urURL" default:"https://data.syncthing.net/newdata"`
+ URPostInsecurely bool `xml:"urPostInsecurely" json:"urPostInsecurely" default:"false"` // For testing
+ URInitialDelayS int `xml:"urInitialDelayS" json:"urInitialDelayS" default:"1800"`
+ RestartOnWakeup bool `xml:"restartOnWakeup" json:"restartOnWakeup" default:"true"`
+ AutoUpgradeIntervalH int `xml:"autoUpgradeIntervalH" json:"autoUpgradeIntervalH" default:"12"` // 0 for off
+ UpgradeToPreReleases bool `xml:"upgradeToPreReleases" json:"upgradeToPreReleases"` // when auto upgrades are enabled
+ KeepTemporariesH int `xml:"keepTemporariesH" json:"keepTemporariesH" default:"24"` // 0 for off
+ CacheIgnoredFiles bool `xml:"cacheIgnoredFiles" json:"cacheIgnoredFiles" default:"false"`
+ ProgressUpdateIntervalS int `xml:"progressUpdateIntervalS" json:"progressUpdateIntervalS" default:"5"`
+ SymlinksEnabled bool `xml:"symlinksEnabled" json:"symlinksEnabled" default:"true"`
+ LimitBandwidthInLan bool `xml:"limitBandwidthInLan" json:"limitBandwidthInLan" default:"false"`
+ MinHomeDiskFreePct float64 `xml:"minHomeDiskFreePct" json:"minHomeDiskFreePct" default:"1"`
+ ReleasesURL string `xml:"releasesURL" json:"releasesURL" default:"https://upgrades.syncthing.net/meta.json"`
+ AlwaysLocalNets []string `xml:"alwaysLocalNet" json:"alwaysLocalNets"`
+ OverwriteRemoteDevNames bool `xml:"overwriteRemoteDeviceNamesOnConnect" json:"overwriteRemoteDeviceNamesOnConnect" default:"false"`
+ TempIndexMinBlocks int `xml:"tempIndexMinBlocks" json:"tempIndexMinBlocks" default:"10"`
+ UnackedNotificationIDs []string `xml:"unackedNotificationID" json:"unackedNotificationIDs"`
+ TrafficClass int `xml:"trafficClass" json:"trafficClass"`
+ WeakHashSelectionMethod WeakHashSelectionMethod `xml:"weakHashSelectionMethod" json:"weakHashSelectionMethod"`
DeprecatedUPnPEnabled bool `xml:"upnpEnabled,omitempty" json:"-"`
DeprecatedUPnPLeaseM int `xml:"upnpLeaseMinutes,omitempty" json:"-"`
diff --git a/lib/config/testdata/overridenvalues.xml b/lib/config/testdata/overridenvalues.xml
index 166e6638..66e94eb9 100644
--- a/lib/config/testdata/overridenvalues.xml
+++ b/lib/config/testdata/overridenvalues.xml
@@ -34,5 +34,6 @@
https://localhost/releases
true
100
+ never
diff --git a/lib/model/model.go b/lib/model/model.go
index 71e9b91c..5070e63f 100644
--- a/lib/model/model.go
+++ b/lib/model/model.go
@@ -37,6 +37,7 @@ import (
"github.com/syncthing/syncthing/lib/sync"
"github.com/syncthing/syncthing/lib/upgrade"
"github.com/syncthing/syncthing/lib/versioner"
+ "github.com/syncthing/syncthing/lib/weakhash"
"github.com/thejerf/suture"
)
@@ -1813,7 +1814,7 @@ func (m *Model) internalScanFolderSubdirs(folder string, subDirs []string) error
ShortID: m.shortID,
ProgressTickIntervalS: folderCfg.ScanProgressIntervalS,
Cancel: cancel,
- UseWeakHashes: folderCfg.WeakHashThresholdPct < 100,
+ UseWeakHashes: weakhash.Enabled,
})
if err != nil {
diff --git a/lib/model/rwfolder.go b/lib/model/rwfolder.go
index 482dbc79..fb8d249d 100644
--- a/lib/model/rwfolder.go
+++ b/lib/model/rwfolder.go
@@ -1221,23 +1221,34 @@ func (f *sendReceiveFolder) copierRoutine(in <-chan copyBlocksState, pullChan ch
f.model.fmut.RUnlock()
var weakHashFinder *weakhash.Finder
- blocksPercentChanged := 0
- if tot := len(state.file.Blocks); tot > 0 {
- blocksPercentChanged = (tot - state.have) * 100 / tot
- }
- if blocksPercentChanged >= f.WeakHashThresholdPct {
- hashesToFind := make([]uint32, 0, len(state.blocks))
- for _, block := range state.blocks {
- if block.WeakHash != 0 {
- hashesToFind = append(hashesToFind, block.WeakHash)
+ if weakhash.Enabled {
+ blocksPercentChanged := 0
+ if tot := len(state.file.Blocks); tot > 0 {
+ blocksPercentChanged = (tot - state.have) * 100 / tot
+ }
+
+ if blocksPercentChanged >= f.WeakHashThresholdPct {
+ hashesToFind := make([]uint32, 0, len(state.blocks))
+ for _, block := range state.blocks {
+ if block.WeakHash != 0 {
+ hashesToFind = append(hashesToFind, block.WeakHash)
+ }
}
- }
- weakHashFinder, err = weakhash.NewFinder(state.realName, protocol.BlockSize, hashesToFind)
- if err != nil {
- l.Debugln("weak hasher", err)
+ if len(hashesToFind) > 0 {
+ weakHashFinder, err = weakhash.NewFinder(state.realName, protocol.BlockSize, hashesToFind)
+ if err != nil {
+ l.Debugln("weak hasher", err)
+ }
+ } else {
+ l.Debugf("not weak hashing %s. file did not contain any weak hashes", state.file.Name)
+ }
+ } else {
+ l.Debugf("not weak hashing %s. not enough changed %.02f < %d", state.file.Name, blocksPercentChanged, f.WeakHashThresholdPct)
}
+ } else {
+ l.Debugf("not weak hashing %s. weak hashing disabled", state.file.Name)
}
for _, block := range state.blocks {
diff --git a/lib/util/utils.go b/lib/util/utils.go
index 353cd803..ffa03caa 100644
--- a/lib/util/utils.go
+++ b/lib/util/utils.go
@@ -25,6 +25,17 @@ func SetDefaults(data interface{}) error {
v := tag.Get("default")
if len(v) > 0 {
+ if parser, ok := f.Interface().(interface {
+ ParseDefault(string) (interface{}, error)
+ }); ok {
+ val, err := parser.ParseDefault(v)
+ if err != nil {
+ panic(err)
+ }
+ f.Set(reflect.ValueOf(val))
+ continue
+ }
+
switch f.Interface().(type) {
case string:
f.SetString(v)
diff --git a/lib/util/utils_test.go b/lib/util/utils_test.go
index 293c79e2..76400a94 100644
--- a/lib/util/utils_test.go
+++ b/lib/util/utils_test.go
@@ -8,12 +8,21 @@ package util
import "testing"
+type Defaulter struct {
+ Value string
+}
+
+func (Defaulter) ParseDefault(v string) (interface{}, error) {
+ return Defaulter{Value: v}, nil
+}
+
func TestSetDefaults(t *testing.T) {
x := &struct {
- A string `default:"string"`
- B int `default:"2"`
- C float64 `default:"2.2"`
- D bool `default:"true"`
+ A string `default:"string"`
+ B int `default:"2"`
+ C float64 `default:"2.2"`
+ D bool `default:"true"`
+ E Defaulter `default:"defaulter"`
}{}
if x.A != "" {
@@ -24,6 +33,8 @@ func TestSetDefaults(t *testing.T) {
t.Errorf("float failed")
} else if x.D {
t.Errorf("bool failed")
+ } else if x.E.Value != "" {
+ t.Errorf("defaulter failed")
}
if err := SetDefaults(x); err != nil {
@@ -38,6 +49,8 @@ func TestSetDefaults(t *testing.T) {
t.Errorf("float failed")
} else if !x.D {
t.Errorf("bool failed")
+ } else if x.E.Value != "defaulter" {
+ t.Errorf("defaulter failed")
}
}
diff --git a/lib/weakhash/weakhash.go b/lib/weakhash/weakhash.go
index 36450920..19065244 100644
--- a/lib/weakhash/weakhash.go
+++ b/lib/weakhash/weakhash.go
@@ -21,11 +21,15 @@ const (
maxWeakhashFinderHits = 10
)
+var (
+ Enabled = true
+)
+
// Find finds all the blocks of the given size within io.Reader that matches
// the hashes provided, and returns a hash -> slice of offsets within reader
// map, that produces the same weak hash.
func Find(ir io.Reader, hashesToFind []uint32, size int) (map[uint32][]int64, error) {
- if ir == nil {
+ if ir == nil || len(hashesToFind) == 0 {
return nil, nil
}
@@ -45,7 +49,7 @@ func Find(ir io.Reader, hashesToFind []uint32, size int) (map[uint32][]int64, er
offsets := make(map[uint32][]int64)
for _, hashToFind := range hashesToFind {
- offsets[hashToFind] = nil
+ offsets[hashToFind] = make([]int64, 0, maxWeakhashFinderHits)
}
var i int64