1 // Copyright (c) Microsoft Corporation. All rights reserved.
2 // Licensed under the MIT license.
3 
4 using System;
5 using System.Collections.Generic;
6 using System.Diagnostics;
7 using System.IO;
8 using System.Linq;
9 using Microsoft.Research.SEAL;
10 
11 namespace SEALNetExamples
12 {
13     partial class Examples
14     {
BFVPerformanceTest(SEALContext context)15         private static void BFVPerformanceTest(SEALContext context)
16         {
17             Stopwatch timer;
18             Utilities.PrintParameters(context);
19             Console.WriteLine();
20 
21             bool hasZLIB = Serialization.IsSupportedComprMode(ComprModeType.ZLIB);
22             bool hasZSTD = Serialization.IsSupportedComprMode(ComprModeType.ZSTD);
23 
24             using EncryptionParameters parms = context.FirstContextData.Parms;
25             using Modulus plainModulus = parms.PlainModulus;
26             ulong polyModulusDegree = parms.PolyModulusDegree;
27 
28             Console.Write("Generating secret/public keys: ");
29             using KeyGenerator keygen = new KeyGenerator(context);
30             Console.WriteLine("Done");
31 
32             using SecretKey secretKey = keygen.SecretKey;
33             keygen.CreatePublicKey(out PublicKey publicKey);
34 
35             Func<RelinKeys> GetRelinKeys = () => {
36                 if (context.UsingKeyswitching)
37                 {
38                     /*
39                     Generate relinearization keys.
40                     */
41                     Console.Write("Generating relinearization keys: ");
42                     timer = Stopwatch.StartNew();
43                     keygen.CreateRelinKeys(out RelinKeys relinKeys);
44                     int micros = (int)(timer.Elapsed.TotalMilliseconds * 1000);
45                     Console.WriteLine($"Done [{micros} microseconds]");
46                     return relinKeys;
47                 }
48                 else
49                 {
50                     return null;
51                 }
52             };
53 
54             Func<GaloisKeys> GetGaloisKeys = () => {
55                 if (context.UsingKeyswitching)
56                 {
57                     if (!context.KeyContextData.Qualifiers.UsingBatching)
58                     {
59                         Console.WriteLine("Given encryption parameters do not support batching.");
60                         return null;
61                     }
62 
63                     /*
64                     Generate Galois keys. In larger examples the Galois keys can use a lot of
65                     memory, which can be a problem in constrained systems. The user should
66                     try some of the larger runs of the test and observe their effect on the
67                     memory pool allocation size. The key generation can also take a long time,
68                     as can be observed from the print-out.
69                     */
70                     Console.Write($"Generating Galois keys: ");
71                     timer = Stopwatch.StartNew();
72                     keygen.CreateGaloisKeys(out GaloisKeys galoisKeys);
73                     int micros = (int)(timer.Elapsed.TotalMilliseconds * 1000);
74                     Console.WriteLine($"Done [{micros} microseconds]");
75                     return galoisKeys;
76                 }
77                 else
78                 {
79                     return null;
80                 }
81             };
82 
83             using RelinKeys relinKeys = GetRelinKeys();
84             using GaloisKeys galKeys = GetGaloisKeys();
85 
86             using Encryptor encryptor = new Encryptor(context, publicKey);
87             using Decryptor decryptor = new Decryptor(context, secretKey);
88             using Evaluator evaluator = new Evaluator(context);
89             using BatchEncoder batchEncoder = new BatchEncoder(context);
90 
91             /*
92             These will hold the total times used by each operation.
93             */
94             Stopwatch timeBatchSum = new Stopwatch();
95             Stopwatch timeUnbatchSum = new Stopwatch();
96             Stopwatch timeEncryptSum = new Stopwatch();
97             Stopwatch timeDecryptSum = new Stopwatch();
98             Stopwatch timeAddSum = new Stopwatch();
99             Stopwatch timeMultiplySum = new Stopwatch();
100             Stopwatch timeMultiplyPlainSum = new Stopwatch();
101             Stopwatch timeSquareSum = new Stopwatch();
102             Stopwatch timeRelinearizeSum = new Stopwatch();
103             Stopwatch timeRotateRowsOneStepSum = new Stopwatch();
104             Stopwatch timeRotateRowsRandomSum = new Stopwatch();
105             Stopwatch timeRotateColumnsSum = new Stopwatch();
106             Stopwatch timeSerializeSum = new Stopwatch();
107             Stopwatch timeSerializeZLIBSum = new Stopwatch();
108             Stopwatch timeSerializeZSTDSum = new Stopwatch();
109 
110             /*
111             How many times to run the test?
112             */
113             int count = 10;
114 
115             /*
116             Populate a vector of values to batch.
117             */
118             ulong slotCount = batchEncoder.SlotCount;
119             ulong[] podValues = new ulong[slotCount];
120             Random rnd = new Random();
121             for (ulong i = 0; i < batchEncoder.SlotCount; i++)
122             {
123                 podValues[i] = plainModulus.Reduce((ulong)rnd.Next());
124             }
125 
126             Console.Write("Running tests ");
127             for (int i = 0; i < count; i++)
128             {
129                 /*
130                 [Batching]
131                 There is nothing unusual here. We batch our random plaintext matrix
132                 into the polynomial. Note how the plaintext we create is of the exactly
133                 right size so unnecessary reallocations are avoided.
134                 */
135                 using Plaintext plain = new Plaintext(parms.PolyModulusDegree, 0);
136                 timeBatchSum.Start();
137                 batchEncoder.Encode(podValues, plain);
138                 timeBatchSum.Stop();
139 
140                 /*
141                 [Unbatching]
142                 We unbatch what we just batched.
143                 */
144                 List<ulong> podList = new List<ulong>((int)slotCount);
145                 timeUnbatchSum.Start();
146                 batchEncoder.Decode(plain, podList);
147                 timeUnbatchSum.Stop();
148                 if (!podList.SequenceEqual(podValues))
149                 {
150                     throw new InvalidOperationException("Batch/unbatch failed. Something is wrong.");
151                 }
152 
153                 /*
154                 [Encryption]
155                 We make sure our ciphertext is already allocated and large enough
156                 to hold the encryption with these encryption parameters. We encrypt
157                 our random batched matrix here.
158                 */
159                 using Ciphertext encrypted = new Ciphertext(context);
160                 timeEncryptSum.Start();
161                 encryptor.Encrypt(plain, encrypted);
162                 timeEncryptSum.Stop();
163 
164                 /*
165                 [Decryption]
166                 We decrypt what we just encrypted.
167                 */
168                 using Plaintext plain2 = new Plaintext(polyModulusDegree, 0);
169                 timeDecryptSum.Start();
170                 decryptor.Decrypt(encrypted, plain2);
171                 timeDecryptSum.Stop();
172                 if (!plain2.Equals(plain))
173                 {
174                     throw new InvalidOperationException("Encrypt/decrypt failed. Something is wrong.");
175                 }
176 
177                 /*
178                 [Add]
179                 We create two ciphertexts and perform a few additions with them.
180                 */
181                 using Plaintext plain1 = new Plaintext(parms.PolyModulusDegree, 0);
182                 for (ulong j = 0; j < batchEncoder.SlotCount; j++)
183                 {
184                     podValues[j] = j;
185                 }
186                 batchEncoder.Encode(podValues, plain1);
187                 for (ulong j = 0; j < batchEncoder.SlotCount; j++)
188                 {
189                     podValues[j] = j + 1;
190                 }
191                 batchEncoder.Encode(podValues, plain2);
192                 using Ciphertext encrypted1 = new Ciphertext(context);
193                 encryptor.Encrypt(plain1, encrypted1);
194                 using Ciphertext encrypted2 = new Ciphertext(context);
195                 encryptor.Encrypt(plain2, encrypted2);
196 
197                 timeAddSum.Start();
198                 evaluator.AddInplace(encrypted1, encrypted1);
199                 evaluator.AddInplace(encrypted2, encrypted2);
200                 evaluator.AddInplace(encrypted1, encrypted2);
201                 timeAddSum.Stop();
202 
203                 /*
204                 [Multiply]
205                 We multiply two ciphertexts. Since the size of the result will be 3,
206                 and will overwrite the first argument, we reserve first enough memory
207                 to avoid reallocating during multiplication.
208                 */
209                 encrypted1.Reserve(3);
210                 timeMultiplySum.Start();
211                 evaluator.MultiplyInplace(encrypted1, encrypted2);
212                 timeMultiplySum.Stop();
213 
214                 /*
215                 [Multiply Plain]
216                 We multiply a ciphertext with a random plaintext. Recall that
217                 MultiplyPlain does not change the size of the ciphertext so we use
218                 encrypted2 here.
219                 */
220                 timeMultiplyPlainSum.Start();
221                 evaluator.MultiplyPlainInplace(encrypted2, plain);
222                 timeMultiplyPlainSum.Stop();
223 
224                 /*
225                 [Square]
226                 We continue to use encrypted2. Now we square it; this should be
227                 faster than generic homomorphic multiplication.
228                 */
229                 timeSquareSum.Start();
230                 evaluator.SquareInplace(encrypted2);
231                 timeSquareSum.Stop();
232 
233                 if (context.UsingKeyswitching)
234                 {
235                     /*
236                     [Relinearize]
237                     Time to get back to encrypted1. We now relinearize it back
238                     to size 2. Since the allocation is currently big enough to
239                     contain a ciphertext of size 3, no costly reallocations are
240                     needed in the process.
241                     */
242                     timeRelinearizeSum.Start();
243                     evaluator.RelinearizeInplace(encrypted1, relinKeys);
244                     timeRelinearizeSum.Stop();
245 
246                     /*
247                     [Rotate Rows One Step]
248                     We rotate matrix rows by one step left and measure the time.
249                     */
250                     timeRotateRowsOneStepSum.Start();
251                     evaluator.RotateRowsInplace(encrypted, 1, galKeys);
252                     evaluator.RotateRowsInplace(encrypted, -1, galKeys);
253                     timeRotateRowsOneStepSum.Stop();
254 
255                     /*
256                     [Rotate Rows Random]
257                     We rotate matrix rows by a random number of steps. This is much more
258                     expensive than rotating by just one step.
259                     */
260                     int rowSize = (int)batchEncoder.SlotCount / 2;
261                     // rowSize is always a power of 2.
262                     int randomRotation = rnd.Next() & (rowSize - 1);
263                     timeRotateRowsRandomSum.Start();
264                     evaluator.RotateRowsInplace(encrypted, randomRotation, galKeys);
265                     timeRotateRowsRandomSum.Stop();
266 
267                     /*
268                     [Rotate Columns]
269                     Nothing surprising here.
270                     */
271                     timeRotateColumnsSum.Start();
272                     evaluator.RotateColumnsInplace(encrypted, galKeys);
273                     timeRotateColumnsSum.Stop();
274                 }
275 
276                 /*
277                 [Serialize Ciphertext]
278                 */
279                 using MemoryStream stream = new MemoryStream();
280                 timeSerializeSum.Start();
281                 encrypted.Save(stream, ComprModeType.None);
282                 timeSerializeSum.Stop();
283 
284                 if (hasZLIB)
285                 {
286                     /*
287                     [Serialize Ciphertext (ZLIB)]
288                     */
289                     timeSerializeZLIBSum.Start();
290                     encrypted.Save(stream, ComprModeType.ZLIB);
291                     timeSerializeZLIBSum.Stop();
292                 }
293 
294                 if (hasZSTD)
295                 {
296                     /*
297                     [Serialize Ciphertext (Zstandard)]
298                     */
299                     timeSerializeZSTDSum.Start();
300                     encrypted.Save(stream, ComprModeType.ZSTD);
301                     timeSerializeZSTDSum.Stop();
302                 }
303 
304                 /*
305                 Print a dot to indicate progress.
306                 */
307                 Console.Write(".");
308                 Console.Out.Flush();
309             }
310 
311             Console.WriteLine(" Done");
312             Console.WriteLine();
313             Console.Out.Flush();
314 
315             int avgBatch = (int)(timeBatchSum.Elapsed.TotalMilliseconds * 1000 / count);
316             int avgUnbatch = (int)(timeUnbatchSum.Elapsed.TotalMilliseconds * 1000 / count);
317             int avgEncrypt = (int)(timeEncryptSum.Elapsed.TotalMilliseconds * 1000 / count);
318             int avgDecrypt = (int)(timeDecryptSum.Elapsed.TotalMilliseconds * 1000 / count);
319             int avgAdd = (int)(timeAddSum.Elapsed.TotalMilliseconds * 1000 / (3 * count));
320             int avgMultiply = (int)(timeMultiplySum.Elapsed.TotalMilliseconds * 1000 / count);
321             int avgMultiplyPlain = (int)(timeMultiplyPlainSum.Elapsed.TotalMilliseconds * 1000 / count);
322             int avgSquare = (int)(timeSquareSum.Elapsed.TotalMilliseconds * 1000 / count);
323             int avgRelinearize = (int)(timeRelinearizeSum.Elapsed.TotalMilliseconds * 1000 / count);
324             int avgRotateRowsOneStep = (int)(timeRotateRowsOneStepSum.Elapsed.TotalMilliseconds * 1000 / (2 * count));
325             int avgRotateRowsRandom = (int)(timeRotateRowsRandomSum.Elapsed.TotalMilliseconds * 1000 / count);
326             int avgRotateColumns = (int)(timeRotateColumnsSum.Elapsed.TotalMilliseconds * 1000 / count);
327             int avgSerializeSum = (int)(timeSerializeSum.Elapsed.TotalMilliseconds * 1000 / count);
328             int avgSerializeZLIBSum = (int)(timeSerializeZLIBSum.Elapsed.TotalMilliseconds * 1000 / count);
329             int avgSerializeZSTDSum = (int)(timeSerializeZSTDSum.Elapsed.TotalMilliseconds * 1000 / count);
330 
331             Console.WriteLine($"Average batch: {avgBatch} microseconds");
332             Console.WriteLine($"Average unbatch: {avgUnbatch} microseconds");
333             Console.WriteLine($"Average encrypt: {avgEncrypt} microseconds");
334             Console.WriteLine($"Average decrypt: {avgDecrypt} microseconds");
335             Console.WriteLine($"Average add: {avgAdd} microseconds");
336             Console.WriteLine($"Average multiply: {avgMultiply} microseconds");
337             Console.WriteLine($"Average multiply plain: {avgMultiplyPlain} microseconds");
338             Console.WriteLine($"Average square: {avgSquare} microseconds");
339             if (context.UsingKeyswitching)
340             {
341                 Console.WriteLine($"Average relinearize: {avgRelinearize} microseconds");
342                 Console.WriteLine($"Average rotate rows one step: {avgRotateRowsOneStep} microseconds");
343                 Console.WriteLine($"Average rotate rows random: {avgRotateRowsRandom} microseconds");
344                 Console.WriteLine($"Average rotate columns: {avgRotateColumns} microseconds");
345             }
346             Console.WriteLine($"Average serialize ciphertext: {avgSerializeSum} microseconds");
347             if (hasZLIB)
348             {
349                 Console.WriteLine(
350                     $"Average compressed (ZLIB) serialize ciphertext: {avgSerializeZLIBSum} microseconds");
351             }
352             if (hasZSTD)
353             {
354                 Console.WriteLine(
355                     $"Average compressed (Zstandard) serialize ciphertext: {avgSerializeZSTDSum} microseconds");
356             }
357 
358             Console.Out.Flush();
359         }
360 
CKKSPerformanceTest(SEALContext context)361         private static void CKKSPerformanceTest(SEALContext context)
362         {
363             Stopwatch timer;
364             Utilities.PrintParameters(context);
365             Console.WriteLine();
366 
367             bool hasZLIB = Serialization.IsSupportedComprMode(ComprModeType.ZLIB);
368             bool hasZSTD = Serialization.IsSupportedComprMode(ComprModeType.ZSTD);
369 
370             using EncryptionParameters parms = context.FirstContextData.Parms;
371             ulong polyModulusDegree = parms.PolyModulusDegree;
372 
373             Console.Write("Generating secret/public keys: ");
374             using KeyGenerator keygen = new KeyGenerator(context);
375             Console.WriteLine("Done");
376 
377             using SecretKey secretKey = keygen.SecretKey;
378             keygen.CreatePublicKey(out PublicKey publicKey);
379 
380             Func<RelinKeys> GetRelinKeys = () => {
381                 if (context.UsingKeyswitching)
382                 {
383                     /*
384                     Generate relinearization keys.
385                     */
386                     Console.Write("Generating relinearization keys: ");
387                     timer = Stopwatch.StartNew();
388                     keygen.CreateRelinKeys(out RelinKeys relinKeys);
389                     int micros = (int)(timer.Elapsed.TotalMilliseconds * 1000);
390                     Console.WriteLine($"Done [{micros} microseconds]");
391                     return relinKeys;
392                 }
393                 else
394                 {
395                     return null;
396                 }
397             };
398 
399             Func<GaloisKeys> GetGaloisKeys = () => {
400                 if (context.UsingKeyswitching)
401                 {
402                     if (!context.KeyContextData.Qualifiers.UsingBatching)
403                     {
404                         Console.WriteLine("Given encryption parameters do not support batching.");
405                         return null;
406                     }
407 
408                     /*
409                     Generate Galois keys. In larger examples the Galois keys can use a lot of
410                     memory, which can be a problem in constrained systems. The user should
411                     try some of the larger runs of the test and observe their effect on the
412                     memory pool allocation size. The key generation can also take a long time,
413                     as can be observed from the print-out.
414                     */
415                     Console.Write($"Generating Galois keys: ");
416                     timer = Stopwatch.StartNew();
417                     keygen.CreateGaloisKeys(out GaloisKeys galoisKeys);
418                     int micros = (int)(timer.Elapsed.TotalMilliseconds * 1000);
419                     Console.WriteLine($"Done [{micros} microseconds]");
420                     return galoisKeys;
421                 }
422                 else
423                 {
424                     return null;
425                 }
426             };
427 
428             using RelinKeys relinKeys = GetRelinKeys();
429             using GaloisKeys galKeys = GetGaloisKeys();
430 
431             using Encryptor encryptor = new Encryptor(context, publicKey);
432             using Decryptor decryptor = new Decryptor(context, secretKey);
433             using Evaluator evaluator = new Evaluator(context);
434             using CKKSEncoder ckksEncoder = new CKKSEncoder(context);
435 
436             Stopwatch timeEncodeSum = new Stopwatch();
437             Stopwatch timeDecodeSum = new Stopwatch();
438             Stopwatch timeEncryptSum = new Stopwatch();
439             Stopwatch timeDecryptSum = new Stopwatch();
440             Stopwatch timeAddSum = new Stopwatch();
441             Stopwatch timeMultiplySum = new Stopwatch();
442             Stopwatch timeMultiplyPlainSum = new Stopwatch();
443             Stopwatch timeSquareSum = new Stopwatch();
444             Stopwatch timeRelinearizeSum = new Stopwatch();
445             Stopwatch timeRescaleSum = new Stopwatch();
446             Stopwatch timeRotateOneStepSum = new Stopwatch();
447             Stopwatch timeRotateRandomSum = new Stopwatch();
448             Stopwatch timeConjugateSum = new Stopwatch();
449             Stopwatch timeSerializeSum = new Stopwatch();
450             Stopwatch timeSerializeZLIBSum = new Stopwatch();
451             Stopwatch timeSerializeZSTDSum = new Stopwatch();
452 
453             Random rnd = new Random();
454 
455             /*
456             How many times to run the test?
457             */
458             int count = 10;
459 
460             /*
461             Populate a vector of floating-point values to batch.
462             */
463             ulong slotCount = ckksEncoder.SlotCount;
464             double[] podValues = new double[slotCount];
465             for (ulong i = 0; i < slotCount; i++)
466             {
467                 podValues[i] = 1.001 * i;
468             }
469 
470             Console.Write("Running tests ");
471             for (int i = 0; i < count; i++)
472             {
473                 /*
474                 [Encoding]
475                 For scale we use the square root of the last CoeffModulus prime
476                 from parms.
477                 */
478                 double scale = Math.Sqrt(parms.CoeffModulus.Last().Value);
479                 using Plaintext plain = new Plaintext(parms.PolyModulusDegree *
480                     (ulong)parms.CoeffModulus.Count(), 0);
481                 timeEncodeSum.Start();
482                 ckksEncoder.Encode(podValues, scale, plain);
483                 timeEncodeSum.Stop();
484 
485                 /*
486                 [Decoding]
487                 */
488                 List<double> podList = new List<double>((int)slotCount);
489                 timeDecodeSum.Start();
490                 ckksEncoder.Decode(plain, podList);
491                 timeDecodeSum.Stop();
492 
493                 /*
494                 [Encryption]
495                 */
496                 using Ciphertext encrypted = new Ciphertext(context);
497                 timeEncryptSum.Start();
498                 encryptor.Encrypt(plain, encrypted);
499                 timeEncryptSum.Stop();
500 
501                 /*
502                 [Decryption]
503                 */
504                 using Plaintext plain2 = new Plaintext(polyModulusDegree, 0);
505                 timeDecryptSum.Start();
506                 decryptor.Decrypt(encrypted, plain2);
507                 timeDecryptSum.Stop();
508 
509                 /*
510                 [Add]
511                 */
512                 using Ciphertext encrypted1 = new Ciphertext(context);
513                 ckksEncoder.Encode(i + 1, plain);
514                 encryptor.Encrypt(plain, encrypted1);
515                 using Ciphertext encrypted2 = new Ciphertext(context);
516                 ckksEncoder.Encode(i + 1, plain2);
517                 encryptor.Encrypt(plain2, encrypted2);
518                 timeAddSum.Start();
519                 evaluator.AddInplace(encrypted1, encrypted2);
520                 evaluator.AddInplace(encrypted2, encrypted2);
521                 evaluator.AddInplace(encrypted1, encrypted2);
522                 timeAddSum.Stop();
523 
524                 /*
525                 [Multiply]
526                 */
527                 encrypted1.Reserve(3);
528                 timeMultiplySum.Start();
529                 evaluator.MultiplyInplace(encrypted1, encrypted2);
530                 timeMultiplySum.Stop();
531 
532                 /*
533                 [Multiply Plain]
534                 */
535                 timeMultiplyPlainSum.Start();
536                 evaluator.MultiplyPlainInplace(encrypted2, plain);
537                 timeMultiplyPlainSum.Stop();
538 
539                 /*
540                 [Square]
541                 */
542                 timeSquareSum.Start();
543                 evaluator.SquareInplace(encrypted2);
544                 timeSquareSum.Stop();
545 
546                 if (context.UsingKeyswitching)
547                 {
548                     /*
549                     [Relinearize]
550                     */
551                     timeRelinearizeSum.Start();
552                     evaluator.RelinearizeInplace(encrypted1, relinKeys);
553                     timeRelinearizeSum.Stop();
554 
555                     /*
556                     [Rescale]
557                     */
558                     timeRescaleSum.Start();
559                     evaluator.RescaleToNextInplace(encrypted1);
560                     timeRescaleSum.Stop();
561 
562                     /*
563                     [Rotate Vector]
564                     */
565                     timeRotateOneStepSum.Start();
566                     evaluator.RotateVectorInplace(encrypted, 1, galKeys);
567                     evaluator.RotateVectorInplace(encrypted, -1, galKeys);
568                     timeRotateOneStepSum.Stop();
569 
570                     /*
571                     [Rotate Vector Random]
572                     */
573                     // ckksEncoder.SlotCount is always a power of 2.
574                     int randomRotation = rnd.Next() & ((int)ckksEncoder.SlotCount - 1);
575                     timeRotateRandomSum.Start();
576                     evaluator.RotateVectorInplace(encrypted, randomRotation, galKeys);
577                     timeRotateRandomSum.Stop();
578 
579                     /*
580                     [Complex Conjugate]
581                     */
582                     timeConjugateSum.Start();
583                     evaluator.ComplexConjugateInplace(encrypted, galKeys);
584                     timeConjugateSum.Stop();
585                 }
586 
587                 /*
588                 [Serialize Ciphertext]
589                 */
590                 using MemoryStream stream = new MemoryStream();
591                 timeSerializeSum.Start();
592                 encrypted.Save(stream, ComprModeType.None);
593                 timeSerializeSum.Stop();
594 
595                 if (hasZLIB)
596                 {
597                     /*
598                     [Serialize Ciphertext (ZLIB)]
599                     */
600                     timeSerializeZLIBSum.Start();
601                     encrypted.Save(stream, ComprModeType.ZLIB);
602                     timeSerializeZLIBSum.Stop();
603                 }
604 
605                 if (hasZSTD)
606                 {
607                     /*
608                     [Serialize Ciphertext (Zstandard)]
609                     */
610                     timeSerializeZSTDSum.Start();
611                     encrypted.Save(stream, ComprModeType.ZSTD);
612                     timeSerializeZSTDSum.Stop();
613                 }
614 
615                 /*
616                 Print a dot to indicate progress.
617                 */
618                 Console.Write(".");
619                 Console.Out.Flush();
620             }
621 
622             Console.WriteLine(" Done");
623             Console.WriteLine();
624             Console.Out.Flush();
625 
626             int avgEncode = (int)(timeEncodeSum.Elapsed.TotalMilliseconds * 1000 / count);
627             int avgDecode = (int)(timeDecodeSum.Elapsed.TotalMilliseconds * 1000 / count);
628             int avgEncrypt = (int)(timeEncryptSum.Elapsed.TotalMilliseconds * 1000 / count);
629             int avgDecrypt = (int)(timeDecryptSum.Elapsed.TotalMilliseconds * 1000 / count);
630             int avgAdd = (int)(timeAddSum.Elapsed.TotalMilliseconds * 1000 / (3 * count));
631             int avgMultiply = (int)(timeMultiplySum.Elapsed.TotalMilliseconds * 1000 / count);
632             int avgMultiplyPlain = (int)(timeMultiplyPlainSum.Elapsed.TotalMilliseconds * 1000 / count);
633             int avgSquare = (int)(timeSquareSum.Elapsed.TotalMilliseconds * 1000 / count);
634             int avgRelinearize = (int)(timeRelinearizeSum.Elapsed.TotalMilliseconds * 1000 / count);
635             int avgRescale = (int)(timeRescaleSum.Elapsed.TotalMilliseconds * 1000 / count);
636             int avgRotateOneStep = (int)(timeRotateOneStepSum.Elapsed.TotalMilliseconds * 1000 / (2 * count));
637             int avgRotateRandom = (int)(timeRotateRandomSum.Elapsed.TotalMilliseconds * 1000 / count);
638             int avgConjugate = (int)(timeConjugateSum.Elapsed.TotalMilliseconds * 1000 / count);
639             int avgSerializeSum = (int)(timeSerializeSum.Elapsed.TotalMilliseconds * 1000 / count);
640             int avgSerializeZLIBSum = (int)(timeSerializeZLIBSum.Elapsed.TotalMilliseconds * 1000 / count);
641             int avgSerializeZSTDSum = (int)(timeSerializeZSTDSum.Elapsed.TotalMilliseconds * 1000 / count);
642 
643             Console.WriteLine($"Average encode: {avgEncode} microseconds");
644             Console.WriteLine($"Average decode: {avgDecode} microseconds");
645             Console.WriteLine($"Average encrypt: {avgEncrypt} microseconds");
646             Console.WriteLine($"Average decrypt: {avgDecrypt} microseconds");
647             Console.WriteLine($"Average add: {avgAdd} microseconds");
648             Console.WriteLine($"Average multiply: {avgMultiply} microseconds");
649             Console.WriteLine($"Average multiply plain: {avgMultiplyPlain} microseconds");
650             Console.WriteLine($"Average square: {avgSquare} microseconds");
651             if (context.UsingKeyswitching)
652             {
653                 Console.WriteLine($"Average relinearize: {avgRelinearize} microseconds");
654                 Console.WriteLine($"Average rescale: {avgRescale} microseconds");
655                 Console.WriteLine($"Average rotate vector one step: {avgRotateOneStep} microseconds");
656                 Console.WriteLine($"Average rotate vector random: {avgRotateRandom} microseconds");
657                 Console.WriteLine($"Average complex conjugate: {avgConjugate} microseconds");
658             }
659             Console.WriteLine($"Average serialize ciphertext: {avgSerializeSum} microseconds");
660             if (hasZLIB)
661             {
662                 Console.WriteLine(
663                     $"Average compressed (ZLIB) serialize ciphertext: {avgSerializeZLIBSum} microseconds");
664             }
665             if (hasZSTD)
666             {
667                 Console.WriteLine(
668                     $"Average compressed (Zstandard) serialize ciphertext: {avgSerializeZSTDSum} microseconds");
669             }
670 
671             Console.Out.Flush();
672         }
673 
ExampleBFVPerformanceDefault()674         private static void ExampleBFVPerformanceDefault()
675         {
676             Utilities.PrintExampleBanner("BFV Performance Test with Degrees: 4096, 8192, and 16384");
677 
678             using EncryptionParameters parms = new EncryptionParameters(SchemeType.BFV);
679             ulong polyModulusDegree = 4096;
680             parms.PolyModulusDegree = polyModulusDegree;
681             parms.CoeffModulus = CoeffModulus.BFVDefault(polyModulusDegree);
682             parms.PlainModulus = new Modulus(786433);
683             using (SEALContext context = new SEALContext(parms))
684             {
685                 BFVPerformanceTest(context);
686             }
687 
688             Console.WriteLine();
689             polyModulusDegree = 8192;
690             parms.PolyModulusDegree = polyModulusDegree;
691             parms.CoeffModulus = CoeffModulus.BFVDefault(polyModulusDegree);
692             parms.PlainModulus = new Modulus(786433);
693             using (SEALContext context = new SEALContext(parms))
694             {
695                 BFVPerformanceTest(context);
696             }
697 
698             Console.WriteLine();
699             polyModulusDegree = 16384;
700             parms.PolyModulusDegree = polyModulusDegree;
701             parms.CoeffModulus = CoeffModulus.BFVDefault(polyModulusDegree);
702             parms.PlainModulus = new Modulus(786433);
703             using (SEALContext context = new SEALContext(parms))
704             {
705                 BFVPerformanceTest(context);
706             }
707 
708             /*
709             Comment out the following to run the biggest example.
710             */
711             //Console.WriteLine();
712             //polyModulusDegree = 32768;
713             //parms.PolyModulusDegree = polyModulusDegree;
714             //parms.CoeffModulus = CoeffModulus.BFVDefault(polyModulusDegree);
715             //parms.PlainModulus = new Modulus(786433);
716             //using (SEALContext context = new SEALContext(parms))
717             //{
718             //    BFVPerformanceTest(context);
719             //}
720         }
721 
ExampleBFVPerformanceCustom()722         private static void ExampleBFVPerformanceCustom()
723         {
724             Console.Write("> Set PolyModulusDegree (1024, 2048, 4096, 8192, 16384, or 32768): ");
725             string input = Console.ReadLine();
726             if (!ulong.TryParse(input, out ulong polyModulusDegree))
727             {
728                 Console.WriteLine("Invalid option.");
729                 return;
730             }
731             if (polyModulusDegree < 1024 || polyModulusDegree > 32768 ||
732                 (polyModulusDegree & (polyModulusDegree - 1)) != 0)
733             {
734                 Console.WriteLine("Invalid option.");
735                 return;
736             }
737 
738             string banner = $"BFV Performance Test with Degree: {polyModulusDegree}";
739             Utilities.PrintExampleBanner(banner);
740 
741             using EncryptionParameters parms = new EncryptionParameters(SchemeType.BFV)
742             {
743                 PolyModulusDegree = polyModulusDegree,
744                 CoeffModulus = CoeffModulus.BFVDefault(polyModulusDegree)
745             };
746             if (polyModulusDegree == 1024)
747             {
748                 parms.PlainModulus = new Modulus(12289);
749             }
750             else
751             {
752                 parms.PlainModulus = new Modulus(786433);
753             }
754 
755             using (SEALContext context = new SEALContext(parms))
756             {
757                 BFVPerformanceTest(context);
758             }
759         }
760 
ExampleCKKSPerformanceDefault()761         private static void ExampleCKKSPerformanceDefault()
762         {
763             Utilities.PrintExampleBanner("CKKS Performance Test with Degrees: 4096, 8192, and 16384");
764 
765             // It is not recommended to use BFVDefault primes in CKKS. However, for performance
766             // test, BFVDefault primes are good enough.
767             using EncryptionParameters parms = new EncryptionParameters(SchemeType.CKKS);
768             ulong polyModulusDegree = 4096;
769             parms.PolyModulusDegree = polyModulusDegree;
770             parms.CoeffModulus = CoeffModulus.BFVDefault(polyModulusDegree);
771             using (SEALContext context = new SEALContext(parms))
772             {
773                 CKKSPerformanceTest(context);
774             }
775 
776             Console.WriteLine();
777             polyModulusDegree = 8192;
778             parms.PolyModulusDegree = polyModulusDegree;
779             parms.CoeffModulus = CoeffModulus.BFVDefault(polyModulusDegree);
780             using (SEALContext context = new SEALContext(parms))
781             {
782                 CKKSPerformanceTest(context);
783             }
784 
785             Console.WriteLine();
786             polyModulusDegree = 16384;
787             parms.PolyModulusDegree = polyModulusDegree;
788             parms.CoeffModulus = CoeffModulus.BFVDefault(polyModulusDegree);
789             using (SEALContext context = new SEALContext(parms))
790             {
791                 CKKSPerformanceTest(context);
792             }
793 
794             /*
795             Comment out the following to run the biggest example.
796             */
797             //Console.WriteLine();
798             //polyModulusDegree = 32768;
799             //parms.PolyModulusDegree = polyModulusDegree;
800             //parms.CoeffModulus = CoeffModulus.BFVDefault(polyModulusDegree);
801             //using (SEALContext context = new SEALContext(parms))
802             //{
803             //    CKKSPerformanceTest(context);
804             //}
805         }
806 
ExampleCKKSPerformanceCustom()807         private static void ExampleCKKSPerformanceCustom()
808         {
809             Console.Write("> Set PolyModulusDegree (1024, 2048, 4096, 8192, 16384, or 32768): ");
810             string input = Console.ReadLine();
811             if (!ulong.TryParse(input, out ulong polyModulusDegree))
812             {
813                 Console.WriteLine("Invalid option.");
814                 return;
815             }
816             if (polyModulusDegree < 1024 || polyModulusDegree > 32768 ||
817                 (polyModulusDegree & (polyModulusDegree - 1)) != 0)
818             {
819                 Console.WriteLine("Invalid option.");
820                 return;
821             }
822 
823             string banner = $"CKKS Performance Test with Degree: {polyModulusDegree}";
824             Utilities.PrintExampleBanner(banner);
825 
826             using EncryptionParameters parms = new EncryptionParameters(SchemeType.CKKS)
827             {
828                 PolyModulusDegree = polyModulusDegree,
829                 CoeffModulus = CoeffModulus.BFVDefault(polyModulusDegree)
830             };
831 
832             using (SEALContext context = new SEALContext(parms))
833             {
834                 CKKSPerformanceTest(context);
835             }
836         }
837 
ExamplePerformanceTest()838         private static void ExamplePerformanceTest()
839         {
840             Utilities.PrintExampleBanner("Example: Performance Test");
841 
842             if (!Stopwatch.IsHighResolution)
843             {
844                 Console.WriteLine("WARNING: High resolution stopwatch not available in this machine.");
845                 Console.WriteLine("         Timings might not be accurate.");
846             }
847 
848             while (true)
849             {
850                 Console.WriteLine();
851                 Console.WriteLine("Select a scheme (and optionally PolyModulusDegree):");
852                 Console.WriteLine("  1. BFV with default degrees");
853                 Console.WriteLine("  2. BFV with a custom degree");
854                 Console.WriteLine("  3. CKKS with default degrees");
855                 Console.WriteLine("  4. CKKS with a custom degree");
856                 Console.WriteLine("  0. Back to main menu");
857                 Console.WriteLine();
858 
859                 ConsoleKeyInfo key;
860                 do
861                 {
862                     Console.Write("> Run performance test (1 ~ 4) or go back (0): ");
863                     key = Console.ReadKey();
864                     Console.WriteLine();
865                 } while (key.KeyChar < '0' || key.KeyChar > '4');
866                 switch (key.Key)
867                 {
868                     case ConsoleKey.D1:
869                         ExampleBFVPerformanceDefault();
870                         break;
871 
872                     case ConsoleKey.D2:
873                         ExampleBFVPerformanceCustom();
874                         break;
875 
876                     case ConsoleKey.D3:
877                         ExampleCKKSPerformanceDefault();
878                         break;
879 
880                     case ConsoleKey.D4:
881                         ExampleCKKSPerformanceCustom();
882                         break;
883 
884                     case ConsoleKey.D0:
885                         Console.WriteLine();
886                         return;
887 
888                     default:
889                         Console.WriteLine("  [Beep~~] Invalid option: type 0 ~ 4");
890                         break;
891                 }
892             }
893         }
894     }
895 }
896