1 /** 2 * Runtime benchmarking 3 * 4 * Copyright: 5 * (C) 2008-2009 Jack Lloyd 6 * (C) 2014-2015 Etienne Cimon 7 * 8 * License: 9 * Botan is released under the Simplified BSD License (see LICENSE.md) 10 */ 11 module botan.utils.benchmark; 12 13 import botan.constants; 14 static if (BOTAN_HAS_BENCHMARK): 15 16 import botan.algo_factory.algo_factory; 17 import botan.algo_base.buf_comp; 18 import botan.block.block_cipher; 19 import botan.stream.stream_cipher; 20 import botan.modes.aead.aead; 21 import botan.hash.hash; 22 import botan.mac.mac; 23 import std.datetime; 24 import std.conv; 25 import botan.utils.types; 26 import std.datetime; 27 import botan.rng.rng; 28 import memutils.hashmap; 29 // import string; 30 import std.datetime; 31 32 /** 33 * Time aspects of an algorithm/provider 34 * Params: 35 * name = the name of the algorithm to test 36 * af = the algorithm factory used to create objects 37 * provider = the provider to use 38 * rng = the rng to use to generate random inputs 39 * runtime = total time for the benchmark to run 40 * buf_size = size of buffer to benchmark against, in KiB 41 * Returns: results a map from op type to operations per second 42 */ 43 HashMapRef!(string, double) 44 timeAlgorithmOps(in string name, 45 AlgorithmFactory af, 46 in string provider, 47 RandomNumberGenerator rng, 48 Duration runtime, 49 size_t buf_size) 50 { 51 const size_t Mebibyte = 1024*1024; 52 53 SecureVector!ubyte buffer = SecureVector!ubyte(buf_size * 1024); 54 rng.randomize(buffer.ptr, buffer.length); 55 56 const double mb_mult = buffer.length / cast(double)(Mebibyte); 57 58 59 { 60 61 const BlockCipher proto = af.prototypeBlockCipher(name, provider); 62 if (proto) { 63 Unique!BlockCipher bc = proto.clone(); 64 65 const SymmetricKey key = SymmetricKey(rng, bc.maximumKeylength()); 66 67 HashMapRef!(string, double) ret; 68 ret["key schedule"] = timeOp(runtime / 8, { bc.setKey(key); }); 69 ret["encrypt"] = mb_mult * timeOp(runtime / 2, { bc.encrypt(buffer); }); 70 ret["decrypt"] = mb_mult * timeOp(runtime / 2, { bc.decrypt(buffer); }); 71 return ret; 72 } 73 } 74 { 75 const StreamCipher proto = af.prototypeStreamCipher(name, provider); 76 if (proto) { 77 Unique!StreamCipher sc = proto.clone(); 78 79 const SymmetricKey key = SymmetricKey(rng, sc.maximumKeylength()); 80 HashMapRef!(string, double) ret; 81 ret["key schedule"] = timeOp(runtime / 8, { sc.setKey(key); }); 82 ret[""] = mb_mult * timeOp(runtime, { sc.encipher(buffer); }); 83 return ret; 84 } 85 } 86 { 87 const HashFunction proto = af.prototypeHashFunction(name, provider); 88 if (proto) { 89 Unique!HashFunction h = proto.clone(); 90 HashMapRef!(string, double) ret; 91 ret[""] = mb_mult * timeOp(runtime, { h.update(buffer); }); 92 return ret; 93 } 94 } 95 { 96 const MessageAuthenticationCode proto = af.prototypeMac(name, provider); 97 98 if (proto) { 99 Unique!MessageAuthenticationCode mac = proto.clone(); 100 101 const SymmetricKey key = SymmetricKey(rng, mac.maximumKeylength()); 102 HashMapRef!(string, double) ret; 103 ret["key schedule"] =timeOp(runtime / 8, { mac.setKey(key); }); 104 ret[""] = mb_mult * timeOp(runtime, { mac.update(buffer); }); 105 return ret; 106 } 107 } 108 { 109 Unique!AEADMode enc = getAead(name, ENCRYPTION); 110 Unique!AEADMode dec = getAead(name, DECRYPTION); 111 112 if (!enc.isEmpty && !dec.isEmpty) 113 { 114 const SymmetricKey key = SymmetricKey(rng, enc.keySpec().maximumKeylength()); 115 HashMapRef!(string, double) ret; 116 ret["key schedule"] = timeOp(runtime / 4, { enc.setKey(key); dec.setKey(key); }) / 2; 117 ret["encrypt"] = mb_mult * timeOp(runtime / 2, { enc.update(buffer, 0); buffer.resize(buf_size*1024); }); 118 ret["decrypt"] = mb_mult * timeOp(runtime / 2, { dec.update(buffer, 0); buffer.resize(buf_size*1024); }); 119 return ret; 120 } 121 } 122 123 124 return HashMapRef!(string, double)(); 125 } 126 127 /** 128 * Algorithm benchmark 129 * Params: 130 * name = the name of the algorithm to test (cipher, hash, or MAC) 131 * af = the algorithm factory used to create objects 132 * rng = the rng to use to generate random inputs 133 * milliseconds = total time for the benchmark to run 134 * buf_size = size of buffer to benchmark against, in KiB 135 * Returns: results a map from provider to speed in mebibytes per second 136 */ 137 HashMapRef!(string, double) 138 algorithmBenchmark(in string name, 139 AlgorithmFactory af, 140 RandomNumberGenerator rng, 141 Duration milliseconds, 142 size_t buf_size) 143 { 144 const Vector!string providers = af.providersOf(name); 145 146 HashMapRef!(string, double) all_results; // provider . ops/sec 147 148 if (!providers.empty) 149 { 150 const Duration ns_per_provider = milliseconds / providers.length; 151 152 foreach (provider; providers) 153 { 154 auto results = timeAlgorithmOps(name, af, provider, rng, ns_per_provider, buf_size); 155 all_results[provider] = findFirstIn(results, ["", "update", "encrypt"]); 156 } 157 } 158 159 return all_results; 160 } 161 162 163 double timeOp(Duration runtime, void delegate() op) 164 { 165 StopWatch sw; 166 sw.start(); 167 int reps = 0; 168 while (sw.peek().to!Duration < runtime) 169 { 170 op(); 171 ++reps; 172 } 173 sw.stop(); 174 return reps.to!double / sw.peek().seconds.to!double; // ie, return ops per second 175 } 176 177 private double findFirstIn(in HashMapRef!(string, double) m, 178 const ref Vector!string keys) 179 { 180 foreach (key; keys[]) 181 { 182 auto val = m.get(key, double.nan); 183 if (val != double.nan) 184 return val; 185 } 186 187 throw new Exception("algorithmFactory no usable keys found in result"); 188 }