sha256.go 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  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. cryptoSha256 "crypto/sha256"
  9. "encoding/hex"
  10. "fmt"
  11. "hash"
  12. "math/rand"
  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. r := rand.New(rand.NewSource(time.Now().UnixNano()))
  96. r.Read(bs)
  97. t0 := time.Now()
  98. b := 0
  99. for time.Since(t0) < duration {
  100. h.Write(bs)
  101. b += chunkSize
  102. }
  103. h.Sum(nil)
  104. d := time.Since(t0)
  105. return float64(int(float64(b)/d.Seconds()/(1<<20)*100)) / 100
  106. }
  107. func formatRate(rate float64) string {
  108. decimals := 0
  109. if rate < 1 {
  110. decimals = 2
  111. } else if rate < 10 {
  112. decimals = 1
  113. }
  114. return fmt.Sprintf("%.*f MB/s", decimals, rate)
  115. }
  116. func verifyCorrectness() {
  117. // The currently selected algo should in fact perform a SHA256 calculation.
  118. // $ echo "Syncthing Magic Testing Value" | openssl dgst -sha256 -hex
  119. correct := "87f6cfd24131724c6ec43495594c5c22abc7d2b86bcc134bc6f10b7ec3dda4ee"
  120. input := "Syncthing Magic Testing Value\n"
  121. h := New()
  122. h.Write([]byte(input))
  123. sum := hex.EncodeToString(h.Sum(nil))
  124. if sum != correct {
  125. panic("sha256 is broken")
  126. }
  127. arr := Sum256([]byte(input))
  128. sum = hex.EncodeToString(arr[:])
  129. if sum != correct {
  130. panic("sha256 is broken")
  131. }
  132. }