sha256.go 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156
  1. // Copyright (C) 2016 The Syncthing Authors.
  2. //
  3. // This Source Code Form is subject to the terms of the Mozilla Public
  4. // License, v. 2.0. If a copy of the MPL was not distributed with this file,
  5. // You can obtain one at https://mozilla.org/MPL/2.0/.
  6. package sha256
  7. import (
  8. "crypto/rand"
  9. cryptoSha256 "crypto/sha256"
  10. "encoding/hex"
  11. "fmt"
  12. "hash"
  13. "os"
  14. "time"
  15. minioSha256 "github.com/minio/sha256-simd"
  16. "github.com/syncthing/syncthing/lib/logger"
  17. )
  18. var l = logger.DefaultLogger.NewFacility("sha256", "SHA256 hashing package")
  19. const (
  20. benchmarkingIterations = 3
  21. benchmarkingDuration = 150 * time.Millisecond
  22. defaultImpl = "crypto/sha256"
  23. minioImpl = "minio/sha256-simd"
  24. Size = cryptoSha256.Size
  25. )
  26. // May be switched out for another implementation
  27. var (
  28. New = cryptoSha256.New
  29. Sum256 = cryptoSha256.Sum256
  30. )
  31. var (
  32. selectedImpl = defaultImpl
  33. cryptoPerf float64
  34. minioPerf float64
  35. )
  36. func SelectAlgo() {
  37. switch os.Getenv("STHASHING") {
  38. case "":
  39. // When unset, probe for the fastest implementation.
  40. benchmark()
  41. if minioPerf > cryptoPerf {
  42. selectMinio()
  43. }
  44. case "minio":
  45. // When set to "minio", use that.
  46. selectMinio()
  47. default:
  48. // When set to anything else, such as "standard", use the default Go
  49. // implementation. Make sure not to touch the minio
  50. // implementation as it may be disabled for incompatibility reasons.
  51. }
  52. verifyCorrectness()
  53. }
  54. // Report prints a line with the measured hash performance rates for the
  55. // selected and alternate implementation.
  56. func Report() {
  57. var otherImpl string
  58. var selectedRate, otherRate float64
  59. switch selectedImpl {
  60. case defaultImpl:
  61. selectedRate = cryptoPerf
  62. otherRate = minioPerf
  63. otherImpl = minioImpl
  64. case minioImpl:
  65. selectedRate = minioPerf
  66. otherRate = cryptoPerf
  67. otherImpl = defaultImpl
  68. }
  69. if selectedRate == 0 {
  70. return
  71. }
  72. l.Infof("Single thread SHA256 performance is %s using %s (%s using %s).", formatRate(selectedRate), selectedImpl, formatRate(otherRate), otherImpl)
  73. }
  74. func selectMinio() {
  75. New = minioSha256.New
  76. Sum256 = minioSha256.Sum256
  77. selectedImpl = minioImpl
  78. }
  79. func benchmark() {
  80. // Interleave the tests to achieve some sort of fairness if the CPU is
  81. // just in the process of spinning up to full speed.
  82. for i := 0; i < benchmarkingIterations; i++ {
  83. if perf := cpuBenchOnce(benchmarkingDuration, cryptoSha256.New); perf > cryptoPerf {
  84. cryptoPerf = perf
  85. }
  86. if perf := cpuBenchOnce(benchmarkingDuration, minioSha256.New); perf > minioPerf {
  87. minioPerf = perf
  88. }
  89. }
  90. }
  91. func cpuBenchOnce(duration time.Duration, newFn func() hash.Hash) float64 {
  92. chunkSize := 100 * 1 << 10
  93. h := newFn()
  94. bs := make([]byte, chunkSize)
  95. rand.Reader.Read(bs)
  96. t0 := time.Now()
  97. b := 0
  98. for time.Since(t0) < duration {
  99. h.Write(bs)
  100. b += chunkSize
  101. }
  102. h.Sum(nil)
  103. d := time.Since(t0)
  104. return float64(int(float64(b)/d.Seconds()/(1<<20)*100)) / 100
  105. }
  106. func formatRate(rate float64) string {
  107. decimals := 0
  108. if rate < 1 {
  109. decimals = 2
  110. } else if rate < 10 {
  111. decimals = 1
  112. }
  113. return fmt.Sprintf("%.*f MB/s", decimals, rate)
  114. }
  115. func verifyCorrectness() {
  116. // The currently selected algo should in fact perform a SHA256 calculation.
  117. // $ echo "Syncthing Magic Testing Value" | openssl dgst -sha256 -hex
  118. correct := "87f6cfd24131724c6ec43495594c5c22abc7d2b86bcc134bc6f10b7ec3dda4ee"
  119. input := "Syncthing Magic Testing Value\n"
  120. h := New()
  121. h.Write([]byte(input))
  122. sum := hex.EncodeToString(h.Sum(nil))
  123. if sum != correct {
  124. panic("sha256 is broken")
  125. }
  126. arr := Sum256([]byte(input))
  127. sum = hex.EncodeToString(arr[:])
  128. if sum != correct {
  129. panic("sha256 is broken")
  130. }
  131. }