sha256.go 3.6 KB

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