sha256.go 3.5 KB

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