/ benchmarks / bench_elliptic_template.nim
bench_elliptic_template.nim
  1  # Constantine
  2  # Copyright (c) 2018-2019    Status Research & Development GmbH
  3  # Copyright (c) 2020-Present Mamy André-Ratsimbazafy
  4  # Licensed and distributed under either of
  5  #   * MIT license (license terms in the root directory or at http://opensource.org/licenses/MIT).
  6  #   * Apache v2 license (license terms in the root directory or at http://www.apache.org/licenses/LICENSE-2.0).
  7  # at your option. This file may not be copied, modified, or distributed except according to those terms.
  8  
  9  # ############################################################
 10  #
 11  #             Benchmark of elliptic curves
 12  #
 13  # ############################################################
 14  
 15  import
 16    # Internals
 17    ../constantine/platforms/abstractions,
 18    ../constantine/math/config/curves,
 19    ../constantine/math/arithmetic,
 20    ../constantine/math/io/io_bigints,
 21    ../constantine/math/elliptic/[
 22      ec_shortweierstrass_affine,
 23      ec_shortweierstrass_projective,
 24      ec_shortweierstrass_jacobian,
 25      ec_shortweierstrass_jacobian_extended,
 26      ec_shortweierstrass_batch_ops,
 27      ec_scalar_mul, ec_endomorphism_accel],
 28      ../constantine/math/constants/zoo_subgroups,
 29    # Helpers
 30    ../helpers/prng_unsafe,
 31    ./platforms,
 32    ./bench_blueprint,
 33    # Reference unsafe scalar multiplication
 34    ../constantine/math/elliptic/ec_scalar_mul_vartime
 35  
 36  export notes
 37  export abstractions # generic sandwich on SecretBool and SecretBool in Jacobian sum
 38  
 39  proc separator*() = separator(179)
 40  
 41  macro fixEllipticDisplay(EC: typedesc): untyped =
 42    # At compile-time, enums are integers and their display is buggy
 43    # we get the Curve ID instead of the curve name.
 44    let instantiated = EC.getTypeInst()
 45    var name = $instantiated[1][0] # EllipticEquationFormCoordinates
 46    let fieldName = $instantiated[1][1][0]
 47    let curveName = $Curve(instantiated[1][1][1].intVal)
 48    name.add "[" & fieldName & "[" & curveName & "]]"
 49    result = newLit name
 50  
 51  proc report(op, elliptic: string, start, stop: MonoTime, startClk, stopClk: int64, iters: int) =
 52    let ns = inNanoseconds((stop-start) div iters)
 53    let throughput = 1e9 / float64(ns)
 54    when SupportsGetTicks:
 55      echo &"{op:<68} {elliptic:<36} {throughput:>15.3f} ops/s {ns:>16} ns/op {(stopClk - startClk) div iters:>12} CPU cycles (approx)"
 56    else:
 57      echo &"{op:<68} {elliptic:<36} {throughput:>15.3f} ops/s {ns:>16} ns/op"
 58  
 59  template bench*(op: string, EC: typedesc, iters: int, body: untyped): untyped =
 60    measure(iters, startTime, stopTime, startClk, stopClk, body)
 61    report(op, fixEllipticDisplay(EC), startTime, stopTime, startClk, stopClk, iters)
 62  
 63  func `+=`[F; G: static Subgroup](P: var ECP_ShortW_JacExt[F, G], Q: ECP_ShortW_JacExt[F, G]) {.inline.}=
 64    P.sum_vartime(P, Q)
 65  func `+=`[F; G: static Subgroup](P: var ECP_ShortW_JacExt[F, G], Q: ECP_ShortW_Aff[F, G]) {.inline.}=
 66    P.madd_vartime(P, Q)
 67  
 68  proc addBench*(EC: typedesc, iters: int) =
 69    var r {.noInit.}: EC
 70    let P = rng.random_unsafe(EC)
 71    let Q = rng.random_unsafe(EC)
 72  
 73    when EC is ECP_ShortW_JacExt:
 74      bench("EC Add vartime " & $EC.G, EC, iters):
 75        r.sum_vartime(P, Q)
 76    else:
 77      block:
 78        bench("EC Add " & $EC.G, EC, iters):
 79          r.sum(P, Q)
 80      block:
 81        bench("EC Add vartime " & $EC.G, EC, iters):
 82          r.sum_vartime(P, Q)
 83  
 84  proc mixedAddBench*(EC: typedesc, iters: int) =
 85    var r {.noInit.}: EC
 86    let P = rng.random_unsafe(EC)
 87    let Q = rng.random_unsafe(EC)
 88    var Qaff: ECP_ShortW_Aff[EC.F, EC.G]
 89    Qaff.affine(Q)
 90  
 91    when EC is ECP_ShortW_JacExt:
 92      bench("EC Mixed Addition vartime " & $EC.G, EC, iters):
 93        r.madd_vartime(P, Qaff)
 94    else:
 95      block:
 96        bench("EC Mixed Addition " & $EC.G, EC, iters):
 97          r.madd(P, Qaff)
 98      block:
 99        bench("EC Mixed Addition vartime " & $EC.G, EC, iters):
100          r.madd_vartime(P, Qaff)
101  
102  proc doublingBench*(EC: typedesc, iters: int) =
103    var r {.noInit.}: EC
104    let P = rng.random_unsafe(EC)
105    bench("EC Double " & $EC.G, EC, iters):
106      r.double(P)
107  
108  proc affFromProjBench*(EC: typedesc, iters: int) =
109    var r {.noInit.}: ECP_ShortW_Aff[EC.F, EC.G]
110    let P = rng.random_unsafe(EC)
111    bench("EC Projective to Affine " & $EC.G, EC, iters):
112      r.affine(P)
113  
114  proc affFromJacBench*(EC: typedesc, iters: int) =
115    var r {.noInit.}: ECP_ShortW_Aff[EC.F, EC.G]
116    let P = rng.random_unsafe(EC)
117    bench("EC Jacobian to Affine " & $EC.G, EC, iters):
118      r.affine(P)
119  
120  proc affFromProjBatchBench*(EC: typedesc, numPoints: int, useBatching: bool, iters: int) =
121    var r = newSeq[affine(EC)](numPoints)
122    var points = newSeq[EC](numPoints)
123  
124    for i in 0 ..< numPoints:
125      points[i] = rng.random_unsafe(EC)
126  
127    if useBatching:
128      bench("EC Projective to Affine -   batched " & $EC.G & " (" & $numPoints & " points)", EC, iters):
129        r.asUnchecked().batchAffine(points.asUnchecked(), numPoints)
130    else:
131      bench("EC Projective to Affine - unbatched " & $EC.G & " (" & $numPoints & " points)", EC, iters):
132        for i in 0 ..< numPoints:
133          r[i].affine(points[i])
134  
135  proc affFromJacBatchBench*(EC: typedesc, numPoints: int, useBatching: bool, iters: int) =
136    var r = newSeq[affine(EC)](numPoints)
137    var points = newSeq[EC](numPoints)
138  
139    for i in 0 ..< numPoints:
140      points[i] = rng.random_unsafe(EC)
141  
142    if useBatching:
143      bench("EC Jacobian to Affine -   batched " & $EC.G & " (" & $numPoints & " points)", EC, iters):
144        r.asUnchecked().batchAffine(points.asUnchecked(), numPoints)
145    else:
146      bench("EC Jacobian to Affine - unbatched " & $EC.G & " (" & $numPoints & " points)", EC, iters):
147        for i in 0 ..< numPoints:
148          r[i].affine(points[i])
149  
150  proc scalarMulGenericBench*(EC: typedesc, bits, window: static int, iters: int) =
151    var r {.noInit.}: EC
152    var P = rng.random_unsafe(EC)
153    P.clearCofactor()
154  
155    let exponent = rng.random_unsafe(BigInt[bits])
156  
157    bench("EC ScalarMul " & $bits & "-bit " & $EC.G & " (window-" & $window & ", generic)", EC, iters):
158      r = P
159      r.scalarMulGeneric(exponent, window)
160  
161  proc scalarMulEndo*(EC: typedesc, bits: static int, iters: int) =
162    var r {.noInit.}: EC
163    var P = rng.random_unsafe(EC)
164    P.clearCofactor()
165  
166    let exponent = rng.random_unsafe(BigInt[bits])
167  
168    bench("EC ScalarMul " & $bits & "-bit " & $EC.G & " (endomorphism accelerated)", EC, iters):
169      r = P
170      r.scalarMulEndo(exponent)
171  
172  proc scalarMulEndoWindow*(EC: typedesc, bits: static int, iters: int) =
173    var r {.noInit.}: EC
174    var P = rng.random_unsafe(EC)
175    P.clearCofactor()
176  
177    let exponent = rng.random_unsafe(BigInt[bits])
178  
179    bench("EC ScalarMul " & $bits & "-bit " & $EC.G & " (window-2, endomorphism accelerated)", EC, iters):
180      r = P
181      when EC.F is Fp:
182        r.scalarMulGLV_m2w2(exponent)
183      else:
184        {.error: "Not implemented".}
185  
186  proc scalarMulVartimeDoubleAddBench*(EC: typedesc, bits: static int, iters: int) =
187    var r {.noInit.}: EC
188    var P = rng.random_unsafe(EC)
189    P.clearCofactor()
190  
191    let exponent = rng.random_unsafe(BigInt[bits])
192  
193    bench("EC ScalarMul " & $bits & "-bit " & $EC.G & " (vartime reference DoubleAdd)", EC, iters):
194      r = P
195      r.scalarMul_doubleAdd_vartime(exponent)
196  
197  proc scalarMulVartimeMinHammingWeightRecodingBench*(EC: typedesc, bits: static int, iters: int) =
198    var r {.noInit.}: EC
199    var P = rng.random_unsafe(EC)
200    P.clearCofactor()
201  
202    let exponent = rng.random_unsafe(BigInt[bits])
203  
204    bench("EC ScalarMul " & $bits & "-bit " & $EC.G & " (vartime min Hamming Weight recoding)", EC, iters):
205      r = P
206      r.scalarMul_minHammingWeight_vartime(exponent)
207  
208  proc scalarMulVartimeWNAFBench*(EC: typedesc, bits, window: static int, iters: int) =
209    var r {.noInit.}: EC
210    var P = rng.random_unsafe(EC)
211    P.clearCofactor()
212  
213    let exponent = rng.random_unsafe(BigInt[bits])
214  
215    bench("EC ScalarMul " & $bits & "-bit " & $EC.G & " (vartime wNAF-" & $window & ")", EC, iters):
216      r = P
217      r.scalarMul_minHammingWeight_windowed_vartime(exponent, window)
218  
219  proc scalarMulVartimeEndoWNAFBench*(EC: typedesc, bits, window: static int, iters: int) =
220    var r {.noInit.}: EC
221    var P = rng.random_unsafe(EC)
222    P.clearCofactor()
223  
224    let exponent = rng.random_unsafe(BigInt[bits])
225  
226    bench("EC ScalarMul " & $bits & "-bit " & $EC.G & " (vartime endomorphism + wNAF-" & $window & ")", EC, iters):
227      r = P
228      r.scalarMulEndo_minHammingWeight_windowed_vartime(exponent, window)
229  
230  proc multiAddBench*(EC: typedesc, numPoints: int, useBatching: bool, iters: int) =
231    var points = newSeq[ECP_ShortW_Aff[EC.F, EC.G]](numPoints)
232  
233    for i in 0 ..< numPoints:
234      points[i] = rng.random_unsafe(ECP_ShortW_Aff[EC.F, EC.G])
235  
236    var r{.noInit.}: EC
237  
238    if useBatching:
239      bench("EC Multi Add batched                  " & $EC.G & " (" & $numPoints & " points)", EC, iters):
240        r.sum_reduce_vartime(points)
241    else:
242      bench("EC Multi Mixed-Add unbatched          " & $EC.G & " (" & $numPoints & " points)", EC, iters):
243        r.setInf()
244        for i in 0 ..< numPoints:
245          r += points[i]
246  
247  
248  proc msmBench*(EC: typedesc, numPoints: int, iters: int) =
249    const bits = EC.F.C.getCurveOrderBitwidth()
250    var points = newSeq[ECP_ShortW_Aff[EC.F, EC.G]](numPoints)
251    var scalars = newSeq[BigInt[bits]](numPoints)
252  
253    for i in 0 ..< numPoints:
254      var tmp = rng.random_unsafe(EC)
255      tmp.clearCofactor()
256      points[i].affine(tmp)
257      scalars[i] = rng.random_unsafe(BigInt[bits])
258  
259    var r{.noInit.}: EC
260    var startNaive, stopNaive, startMSMbaseline, stopMSMbaseline, startMSMopt, stopMSMopt: MonoTime
261  
262    if numPoints <= 100000:
263      bench("EC scalar muls                " & align($numPoints, 7) & " (scalars " & $bits & "-bit, points) pairs ", EC, iters):
264        startNaive = getMonotime()
265        var tmp: EC
266        r.setInf()
267        for i in 0 ..< points.len:
268          tmp.fromAffine(points[i])
269          tmp.scalarMul(scalars[i])
270          r += tmp
271        stopNaive = getMonotime()
272  
273    block:
274      bench("EC multi-scalar-mul baseline  " & align($numPoints, 7) & " (scalars " & $bits & "-bit, points) pairs ", EC, iters):
275        startMSMbaseline = getMonotime()
276        r.multiScalarMul_reference_vartime(scalars, points)
277        stopMSMbaseline = getMonotime()
278  
279    block:
280      bench("EC multi-scalar-mul optimized " & align($numPoints, 7) & " (scalars " & $bits & "-bit, points) pairs ", EC, iters):
281        startMSMopt = getMonotime()
282        r.multiScalarMul_vartime(scalars, points)
283        stopMSMopt = getMonotime()
284  
285    let perfNaive = inNanoseconds((stopNaive-startNaive) div iters)
286    let perfMSMbaseline = inNanoseconds((stopMSMbaseline-startMSMbaseline) div iters)
287    let perfMSMopt = inNanoseconds((stopMSMopt-startMSMopt) div iters)
288  
289    if numPoints <= 100000:
290      let speedupBaseline = float(perfNaive) / float(perfMSMbaseline)
291      echo &"Speedup ratio baseline over naive linear combination: {speedupBaseline:>6.3f}x"
292  
293      let speedupOpt = float(perfNaive) / float(perfMSMopt)
294      echo &"Speedup ratio optimized over naive linear combination: {speedupOpt:>6.3f}x"
295  
296    let speedupOptBaseline = float(perfMSMbaseline) / float(perfMSMopt)
297    echo &"Speedup ratio optimized over baseline linear combination: {speedupOptBaseline:>6.3f}x"