1 // Copyright (c) Microsoft Corporation. All rights reserved.
2 // Licensed under the MIT license.
3 
4 #include "examples.h"
5 
6 using namespace std;
7 using namespace seal;
8 
bfv_performance_test(SEALContext context)9 void bfv_performance_test(SEALContext context)
10 {
11     chrono::high_resolution_clock::time_point time_start, time_end;
12 
13     print_parameters(context);
14     cout << endl;
15 
16     auto &parms = context.first_context_data()->parms();
17     auto &plain_modulus = parms.plain_modulus();
18     size_t poly_modulus_degree = parms.poly_modulus_degree();
19 
20     cout << "Generating secret/public keys: ";
21     KeyGenerator keygen(context);
22     cout << "Done" << endl;
23 
24     auto secret_key = keygen.secret_key();
25     PublicKey public_key;
26     keygen.create_public_key(public_key);
27 
28     RelinKeys relin_keys;
29     GaloisKeys gal_keys;
30     chrono::microseconds time_diff;
31     if (context.using_keyswitching())
32     {
33         /*
34         Generate relinearization keys.
35         */
36         cout << "Generating relinearization keys: ";
37         time_start = chrono::high_resolution_clock::now();
38         keygen.create_relin_keys(relin_keys);
39         time_end = chrono::high_resolution_clock::now();
40         time_diff = chrono::duration_cast<chrono::microseconds>(time_end - time_start);
41         cout << "Done [" << time_diff.count() << " microseconds]" << endl;
42 
43         if (!context.key_context_data()->qualifiers().using_batching)
44         {
45             cout << "Given encryption parameters do not support batching." << endl;
46             return;
47         }
48 
49         /*
50         Generate Galois keys. In larger examples the Galois keys can use a lot of
51         memory, which can be a problem in constrained systems. The user should
52         try some of the larger runs of the test and observe their effect on the
53         memory pool allocation size. The key generation can also take a long time,
54         as can be observed from the print-out.
55         */
56         cout << "Generating Galois keys: ";
57         time_start = chrono::high_resolution_clock::now();
58         keygen.create_galois_keys(gal_keys);
59         time_end = chrono::high_resolution_clock::now();
60         time_diff = chrono::duration_cast<chrono::microseconds>(time_end - time_start);
61         cout << "Done [" << time_diff.count() << " microseconds]" << endl;
62     }
63 
64     Encryptor encryptor(context, public_key);
65     Decryptor decryptor(context, secret_key);
66     Evaluator evaluator(context);
67     BatchEncoder batch_encoder(context);
68 
69     /*
70     These will hold the total times used by each operation.
71     */
72     chrono::microseconds time_batch_sum(0);
73     chrono::microseconds time_unbatch_sum(0);
74     chrono::microseconds time_encrypt_sum(0);
75     chrono::microseconds time_decrypt_sum(0);
76     chrono::microseconds time_add_sum(0);
77     chrono::microseconds time_multiply_sum(0);
78     chrono::microseconds time_multiply_plain_sum(0);
79     chrono::microseconds time_square_sum(0);
80     chrono::microseconds time_relinearize_sum(0);
81     chrono::microseconds time_rotate_rows_one_step_sum(0);
82     chrono::microseconds time_rotate_rows_random_sum(0);
83     chrono::microseconds time_rotate_columns_sum(0);
84     chrono::microseconds time_serialize_sum(0);
85 #ifdef SEAL_USE_ZLIB
86     chrono::microseconds time_serialize_zlib_sum(0);
87 #endif
88 #ifdef SEAL_USE_ZSTD
89     chrono::microseconds time_serialize_zstd_sum(0);
90 #endif
91     /*
92     How many times to run the test?
93     */
94     long long count = 10;
95 
96     /*
97     Populate a vector of values to batch.
98     */
99     size_t slot_count = batch_encoder.slot_count();
100     vector<uint64_t> pod_vector;
101     random_device rd;
102     for (size_t i = 0; i < slot_count; i++)
103     {
104         pod_vector.push_back(plain_modulus.reduce(rd()));
105     }
106 
107     cout << "Running tests ";
108     for (size_t i = 0; i < static_cast<size_t>(count); i++)
109     {
110         /*
111         [Batching]
112         There is nothing unusual here. We batch our random plaintext matrix
113         into the polynomial. Note how the plaintext we create is of the exactly
114         right size so unnecessary reallocations are avoided.
115         */
116         Plaintext plain(poly_modulus_degree, 0);
117         Plaintext plain1(poly_modulus_degree, 0);
118         Plaintext plain2(poly_modulus_degree, 0);
119         time_start = chrono::high_resolution_clock::now();
120         batch_encoder.encode(pod_vector, plain);
121         time_end = chrono::high_resolution_clock::now();
122         time_batch_sum += chrono::duration_cast<chrono::microseconds>(time_end - time_start);
123 
124         /*
125         [Unbatching]
126         We unbatch what we just batched.
127         */
128         vector<uint64_t> pod_vector2(slot_count);
129         time_start = chrono::high_resolution_clock::now();
130         batch_encoder.decode(plain, pod_vector2);
131         time_end = chrono::high_resolution_clock::now();
132         time_unbatch_sum += chrono::duration_cast<chrono::microseconds>(time_end - time_start);
133         if (pod_vector2 != pod_vector)
134         {
135             throw runtime_error("Batch/unbatch failed. Something is wrong.");
136         }
137 
138         /*
139         [Encryption]
140         We make sure our ciphertext is already allocated and large enough
141         to hold the encryption with these encryption parameters. We encrypt
142         our random batched matrix here.
143         */
144         Ciphertext encrypted(context);
145         time_start = chrono::high_resolution_clock::now();
146         encryptor.encrypt(plain, encrypted);
147         time_end = chrono::high_resolution_clock::now();
148         time_encrypt_sum += chrono::duration_cast<chrono::microseconds>(time_end - time_start);
149 
150         /*
151         [Decryption]
152         We decrypt what we just encrypted.
153         */
154         time_start = chrono::high_resolution_clock::now();
155         decryptor.decrypt(encrypted, plain2);
156         time_end = chrono::high_resolution_clock::now();
157         time_decrypt_sum += chrono::duration_cast<chrono::microseconds>(time_end - time_start);
158         if (plain2 != plain)
159         {
160             throw runtime_error("Encrypt/decrypt failed. Something is wrong.");
161         }
162 
163         /*
164         [Add]
165         We create two ciphertexts and perform a few additions with them.
166         */
167         Ciphertext encrypted1(context);
168         batch_encoder.encode(vector<uint64_t>(slot_count, i), plain1);
169         encryptor.encrypt(plain1, encrypted1);
170         Ciphertext encrypted2(context);
171         batch_encoder.encode(vector<uint64_t>(slot_count, i + 1), plain2);
172         encryptor.encrypt(plain2, encrypted2);
173         time_start = chrono::high_resolution_clock::now();
174         evaluator.add_inplace(encrypted1, encrypted1);
175         evaluator.add_inplace(encrypted2, encrypted2);
176         evaluator.add_inplace(encrypted1, encrypted2);
177         time_end = chrono::high_resolution_clock::now();
178         time_add_sum += chrono::duration_cast<chrono::microseconds>(time_end - time_start);
179 
180         /*
181         [Multiply]
182         We multiply two ciphertexts. Since the size of the result will be 3,
183         and will overwrite the first argument, we reserve first enough memory
184         to avoid reallocating during multiplication.
185         */
186         encrypted1.reserve(3);
187         time_start = chrono::high_resolution_clock::now();
188         evaluator.multiply_inplace(encrypted1, encrypted2);
189         time_end = chrono::high_resolution_clock::now();
190         time_multiply_sum += chrono::duration_cast<chrono::microseconds>(time_end - time_start);
191 
192         /*
193         [Multiply Plain]
194         We multiply a ciphertext with a random plaintext. Recall that
195         multiply_plain does not change the size of the ciphertext so we use
196         encrypted2 here.
197         */
198         time_start = chrono::high_resolution_clock::now();
199         evaluator.multiply_plain_inplace(encrypted2, plain);
200         time_end = chrono::high_resolution_clock::now();
201         time_multiply_plain_sum += chrono::duration_cast<chrono::microseconds>(time_end - time_start);
202 
203         /*
204         [Square]
205         We continue to use encrypted2. Now we square it; this should be
206         faster than generic homomorphic multiplication.
207         */
208         time_start = chrono::high_resolution_clock::now();
209         evaluator.square_inplace(encrypted2);
210         time_end = chrono::high_resolution_clock::now();
211         time_square_sum += chrono::duration_cast<chrono::microseconds>(time_end - time_start);
212 
213         if (context.using_keyswitching())
214         {
215             /*
216             [Relinearize]
217             Time to get back to encrypted1. We now relinearize it back
218             to size 2. Since the allocation is currently big enough to
219             contain a ciphertext of size 3, no costly reallocations are
220             needed in the process.
221             */
222             time_start = chrono::high_resolution_clock::now();
223             evaluator.relinearize_inplace(encrypted1, relin_keys);
224             time_end = chrono::high_resolution_clock::now();
225             time_relinearize_sum += chrono::duration_cast<chrono::microseconds>(time_end - time_start);
226 
227             /*
228             [Rotate Rows One Step]
229             We rotate matrix rows by one step left and measure the time.
230             */
231             time_start = chrono::high_resolution_clock::now();
232             evaluator.rotate_rows_inplace(encrypted, 1, gal_keys);
233             evaluator.rotate_rows_inplace(encrypted, -1, gal_keys);
234             time_end = chrono::high_resolution_clock::now();
235             time_rotate_rows_one_step_sum += chrono::duration_cast<chrono::microseconds>(time_end - time_start);
236             ;
237 
238             /*
239             [Rotate Rows Random]
240             We rotate matrix rows by a random number of steps. This is much more
241             expensive than rotating by just one step.
242             */
243             size_t row_size = batch_encoder.slot_count() / 2;
244             // row_size is always a power of 2
245             int random_rotation = static_cast<int>(rd() & (row_size - 1));
246             time_start = chrono::high_resolution_clock::now();
247             evaluator.rotate_rows_inplace(encrypted, random_rotation, gal_keys);
248             time_end = chrono::high_resolution_clock::now();
249             time_rotate_rows_random_sum += chrono::duration_cast<chrono::microseconds>(time_end - time_start);
250 
251             /*
252             [Rotate Columns]
253             Nothing surprising here.
254             */
255             time_start = chrono::high_resolution_clock::now();
256             evaluator.rotate_columns_inplace(encrypted, gal_keys);
257             time_end = chrono::high_resolution_clock::now();
258             time_rotate_columns_sum += chrono::duration_cast<chrono::microseconds>(time_end - time_start);
259         }
260 
261         /*
262         [Serialize Ciphertext]
263         */
264         size_t buf_size = static_cast<size_t>(encrypted.save_size(compr_mode_type::none));
265         vector<seal_byte> buf(buf_size);
266         time_start = chrono::high_resolution_clock::now();
267         encrypted.save(buf.data(), buf_size, compr_mode_type::none);
268         time_end = chrono::high_resolution_clock::now();
269         time_serialize_sum += chrono::duration_cast<chrono::microseconds>(time_end - time_start);
270 #ifdef SEAL_USE_ZLIB
271         /*
272         [Serialize Ciphertext (ZLIB)]
273         */
274         buf_size = static_cast<size_t>(encrypted.save_size(compr_mode_type::zlib));
275         buf.resize(buf_size);
276         time_start = chrono::high_resolution_clock::now();
277         encrypted.save(buf.data(), buf_size, compr_mode_type::zlib);
278         time_end = chrono::high_resolution_clock::now();
279         time_serialize_zlib_sum += chrono::duration_cast<chrono::microseconds>(time_end - time_start);
280 #endif
281 #ifdef SEAL_USE_ZSTD
282         /*
283         [Serialize Ciphertext (Zstandard)]
284         */
285         buf_size = static_cast<size_t>(encrypted.save_size(compr_mode_type::zstd));
286         buf.resize(buf_size);
287         time_start = chrono::high_resolution_clock::now();
288         encrypted.save(buf.data(), buf_size, compr_mode_type::zstd);
289         time_end = chrono::high_resolution_clock::now();
290         time_serialize_zstd_sum += chrono::duration_cast<chrono::microseconds>(time_end - time_start);
291 #endif
292         /*
293         Print a dot to indicate progress.
294         */
295         cout << ".";
296         cout.flush();
297     }
298 
299     cout << " Done" << endl << endl;
300     cout.flush();
301 
302     auto avg_batch = time_batch_sum.count() / count;
303     auto avg_unbatch = time_unbatch_sum.count() / count;
304     auto avg_encrypt = time_encrypt_sum.count() / count;
305     auto avg_decrypt = time_decrypt_sum.count() / count;
306     auto avg_add = time_add_sum.count() / (3 * count);
307     auto avg_multiply = time_multiply_sum.count() / count;
308     auto avg_multiply_plain = time_multiply_plain_sum.count() / count;
309     auto avg_square = time_square_sum.count() / count;
310     auto avg_relinearize = time_relinearize_sum.count() / count;
311     auto avg_rotate_rows_one_step = time_rotate_rows_one_step_sum.count() / (2 * count);
312     auto avg_rotate_rows_random = time_rotate_rows_random_sum.count() / count;
313     auto avg_rotate_columns = time_rotate_columns_sum.count() / count;
314     auto avg_serialize = time_serialize_sum.count() / count;
315 #ifdef SEAL_USE_ZLIB
316     auto avg_serialize_zlib = time_serialize_zlib_sum.count() / count;
317 #endif
318 #ifdef SEAL_USE_ZSTD
319     auto avg_serialize_zstd = time_serialize_zstd_sum.count() / count;
320 #endif
321     cout << "Average batch: " << avg_batch << " microseconds" << endl;
322     cout << "Average unbatch: " << avg_unbatch << " microseconds" << endl;
323     cout << "Average encrypt: " << avg_encrypt << " microseconds" << endl;
324     cout << "Average decrypt: " << avg_decrypt << " microseconds" << endl;
325     cout << "Average add: " << avg_add << " microseconds" << endl;
326     cout << "Average multiply: " << avg_multiply << " microseconds" << endl;
327     cout << "Average multiply plain: " << avg_multiply_plain << " microseconds" << endl;
328     cout << "Average square: " << avg_square << " microseconds" << endl;
329     if (context.using_keyswitching())
330     {
331         cout << "Average relinearize: " << avg_relinearize << " microseconds" << endl;
332         cout << "Average rotate rows one step: " << avg_rotate_rows_one_step << " microseconds" << endl;
333         cout << "Average rotate rows random: " << avg_rotate_rows_random << " microseconds" << endl;
334         cout << "Average rotate columns: " << avg_rotate_columns << " microseconds" << endl;
335     }
336     cout << "Average serialize ciphertext: " << avg_serialize << " microseconds" << endl;
337 #ifdef SEAL_USE_ZLIB
338     cout << "Average compressed (ZLIB) serialize ciphertext: " << avg_serialize_zlib << " microseconds" << endl;
339 #endif
340 #ifdef SEAL_USE_ZSTD
341     cout << "Average compressed (Zstandard) serialize ciphertext: " << avg_serialize_zstd << " microseconds" << endl;
342 #endif
343     cout.flush();
344 }
345 
ckks_performance_test(SEALContext context)346 void ckks_performance_test(SEALContext context)
347 {
348     chrono::high_resolution_clock::time_point time_start, time_end;
349 
350     print_parameters(context);
351     cout << endl;
352 
353     auto &parms = context.first_context_data()->parms();
354     size_t poly_modulus_degree = parms.poly_modulus_degree();
355 
356     cout << "Generating secret/public keys: ";
357     KeyGenerator keygen(context);
358     cout << "Done" << endl;
359 
360     auto secret_key = keygen.secret_key();
361     PublicKey public_key;
362     keygen.create_public_key(public_key);
363 
364     RelinKeys relin_keys;
365     GaloisKeys gal_keys;
366     chrono::microseconds time_diff;
367     if (context.using_keyswitching())
368     {
369         cout << "Generating relinearization keys: ";
370         time_start = chrono::high_resolution_clock::now();
371         keygen.create_relin_keys(relin_keys);
372         time_end = chrono::high_resolution_clock::now();
373         time_diff = chrono::duration_cast<chrono::microseconds>(time_end - time_start);
374         cout << "Done [" << time_diff.count() << " microseconds]" << endl;
375 
376         if (!context.first_context_data()->qualifiers().using_batching)
377         {
378             cout << "Given encryption parameters do not support batching." << endl;
379             return;
380         }
381 
382         cout << "Generating Galois keys: ";
383         time_start = chrono::high_resolution_clock::now();
384         keygen.create_galois_keys(gal_keys);
385         time_end = chrono::high_resolution_clock::now();
386         time_diff = chrono::duration_cast<chrono::microseconds>(time_end - time_start);
387         cout << "Done [" << time_diff.count() << " microseconds]" << endl;
388     }
389 
390     Encryptor encryptor(context, public_key);
391     Decryptor decryptor(context, secret_key);
392     Evaluator evaluator(context);
393     CKKSEncoder ckks_encoder(context);
394 
395     chrono::microseconds time_encode_sum(0);
396     chrono::microseconds time_decode_sum(0);
397     chrono::microseconds time_encrypt_sum(0);
398     chrono::microseconds time_decrypt_sum(0);
399     chrono::microseconds time_add_sum(0);
400     chrono::microseconds time_multiply_sum(0);
401     chrono::microseconds time_multiply_plain_sum(0);
402     chrono::microseconds time_square_sum(0);
403     chrono::microseconds time_relinearize_sum(0);
404     chrono::microseconds time_rescale_sum(0);
405     chrono::microseconds time_rotate_one_step_sum(0);
406     chrono::microseconds time_rotate_random_sum(0);
407     chrono::microseconds time_conjugate_sum(0);
408     chrono::microseconds time_serialize_sum(0);
409 #ifdef SEAL_USE_ZLIB
410     chrono::microseconds time_serialize_zlib_sum(0);
411 #endif
412 #ifdef SEAL_USE_ZSTD
413     chrono::microseconds time_serialize_zstd_sum(0);
414 #endif
415     /*
416     How many times to run the test?
417     */
418     long long count = 10;
419 
420     /*
421     Populate a vector of floating-point values to batch.
422     */
423     vector<double> pod_vector;
424     random_device rd;
425     for (size_t i = 0; i < ckks_encoder.slot_count(); i++)
426     {
427         pod_vector.push_back(1.001 * static_cast<double>(i));
428     }
429 
430     cout << "Running tests ";
431     for (long long i = 0; i < count; i++)
432     {
433         /*
434         [Encoding]
435         For scale we use the square root of the last coeff_modulus prime
436         from parms.
437         */
438         Plaintext plain(parms.poly_modulus_degree() * parms.coeff_modulus().size(), 0);
439         /*
440 
441         */
442         double scale = sqrt(static_cast<double>(parms.coeff_modulus().back().value()));
443         time_start = chrono::high_resolution_clock::now();
444         ckks_encoder.encode(pod_vector, scale, plain);
445         time_end = chrono::high_resolution_clock::now();
446         time_encode_sum += chrono::duration_cast<chrono::microseconds>(time_end - time_start);
447 
448         /*
449         [Decoding]
450         */
451         vector<double> pod_vector2(ckks_encoder.slot_count());
452         time_start = chrono::high_resolution_clock::now();
453         ckks_encoder.decode(plain, pod_vector2);
454         time_end = chrono::high_resolution_clock::now();
455         time_decode_sum += chrono::duration_cast<chrono::microseconds>(time_end - time_start);
456 
457         /*
458         [Encryption]
459         */
460         Ciphertext encrypted(context);
461         time_start = chrono::high_resolution_clock::now();
462         encryptor.encrypt(plain, encrypted);
463         time_end = chrono::high_resolution_clock::now();
464         time_encrypt_sum += chrono::duration_cast<chrono::microseconds>(time_end - time_start);
465 
466         /*
467         [Decryption]
468         */
469         Plaintext plain2(poly_modulus_degree, 0);
470         time_start = chrono::high_resolution_clock::now();
471         decryptor.decrypt(encrypted, plain2);
472         time_end = chrono::high_resolution_clock::now();
473         time_decrypt_sum += chrono::duration_cast<chrono::microseconds>(time_end - time_start);
474 
475         /*
476         [Add]
477         */
478         Ciphertext encrypted1(context);
479         ckks_encoder.encode(i + 1, plain);
480         encryptor.encrypt(plain, encrypted1);
481         Ciphertext encrypted2(context);
482         ckks_encoder.encode(i + 1, plain2);
483         encryptor.encrypt(plain2, encrypted2);
484         time_start = chrono::high_resolution_clock::now();
485         evaluator.add_inplace(encrypted1, encrypted1);
486         evaluator.add_inplace(encrypted2, encrypted2);
487         evaluator.add_inplace(encrypted1, encrypted2);
488         time_end = chrono::high_resolution_clock::now();
489         time_add_sum += chrono::duration_cast<chrono::microseconds>(time_end - time_start);
490 
491         /*
492         [Multiply]
493         */
494         encrypted1.reserve(3);
495         time_start = chrono::high_resolution_clock::now();
496         evaluator.multiply_inplace(encrypted1, encrypted2);
497         time_end = chrono::high_resolution_clock::now();
498         time_multiply_sum += chrono::duration_cast<chrono::microseconds>(time_end - time_start);
499 
500         /*
501         [Multiply Plain]
502         */
503         time_start = chrono::high_resolution_clock::now();
504         evaluator.multiply_plain_inplace(encrypted2, plain);
505         time_end = chrono::high_resolution_clock::now();
506         time_multiply_plain_sum += chrono::duration_cast<chrono::microseconds>(time_end - time_start);
507 
508         /*
509         [Square]
510         */
511         time_start = chrono::high_resolution_clock::now();
512         evaluator.square_inplace(encrypted2);
513         time_end = chrono::high_resolution_clock::now();
514         time_square_sum += chrono::duration_cast<chrono::microseconds>(time_end - time_start);
515 
516         if (context.using_keyswitching())
517         {
518             /*
519             [Relinearize]
520             */
521             time_start = chrono::high_resolution_clock::now();
522             evaluator.relinearize_inplace(encrypted1, relin_keys);
523             time_end = chrono::high_resolution_clock::now();
524             time_relinearize_sum += chrono::duration_cast<chrono::microseconds>(time_end - time_start);
525 
526             /*
527             [Rescale]
528             */
529             time_start = chrono::high_resolution_clock::now();
530             evaluator.rescale_to_next_inplace(encrypted1);
531             time_end = chrono::high_resolution_clock::now();
532             time_rescale_sum += chrono::duration_cast<chrono::microseconds>(time_end - time_start);
533 
534             /*
535             [Rotate Vector]
536             */
537             time_start = chrono::high_resolution_clock::now();
538             evaluator.rotate_vector_inplace(encrypted, 1, gal_keys);
539             evaluator.rotate_vector_inplace(encrypted, -1, gal_keys);
540             time_end = chrono::high_resolution_clock::now();
541             time_rotate_one_step_sum += chrono::duration_cast<chrono::microseconds>(time_end - time_start);
542 
543             /*
544             [Rotate Vector Random]
545             */
546             // ckks_encoder.slot_count() is always a power of 2.
547             int random_rotation = static_cast<int>(rd() & (ckks_encoder.slot_count() - 1));
548             time_start = chrono::high_resolution_clock::now();
549             evaluator.rotate_vector_inplace(encrypted, random_rotation, gal_keys);
550             time_end = chrono::high_resolution_clock::now();
551             time_rotate_random_sum += chrono::duration_cast<chrono::microseconds>(time_end - time_start);
552 
553             /*
554             [Complex Conjugate]
555             */
556             time_start = chrono::high_resolution_clock::now();
557             evaluator.complex_conjugate_inplace(encrypted, gal_keys);
558             time_end = chrono::high_resolution_clock::now();
559             time_conjugate_sum += chrono::duration_cast<chrono::microseconds>(time_end - time_start);
560         }
561 
562         /*
563         [Serialize Ciphertext]
564         */
565         size_t buf_size = static_cast<size_t>(encrypted.save_size(compr_mode_type::none));
566         vector<seal_byte> buf(buf_size);
567         time_start = chrono::high_resolution_clock::now();
568         encrypted.save(buf.data(), buf_size, compr_mode_type::none);
569         time_end = chrono::high_resolution_clock::now();
570         time_serialize_sum += chrono::duration_cast<chrono::microseconds>(time_end - time_start);
571 #ifdef SEAL_USE_ZLIB
572         /*
573         [Serialize Ciphertext (ZLIB)]
574         */
575         buf_size = static_cast<size_t>(encrypted.save_size(compr_mode_type::zlib));
576         buf.resize(buf_size);
577         time_start = chrono::high_resolution_clock::now();
578         encrypted.save(buf.data(), buf_size, compr_mode_type::zlib);
579         time_end = chrono::high_resolution_clock::now();
580         time_serialize_zlib_sum += chrono::duration_cast<chrono::microseconds>(time_end - time_start);
581 #endif
582 #ifdef SEAL_USE_ZSTD
583         /*
584         [Serialize Ciphertext (Zstandard)]
585         */
586         buf_size = static_cast<size_t>(encrypted.save_size(compr_mode_type::zstd));
587         buf.resize(buf_size);
588         time_start = chrono::high_resolution_clock::now();
589         encrypted.save(buf.data(), buf_size, compr_mode_type::zstd);
590         time_end = chrono::high_resolution_clock::now();
591         time_serialize_zstd_sum += chrono::duration_cast<chrono::microseconds>(time_end - time_start);
592 #endif
593         /*
594         Print a dot to indicate progress.
595         */
596         cout << ".";
597         cout.flush();
598     }
599 
600     cout << " Done" << endl << endl;
601     cout.flush();
602 
603     auto avg_encode = time_encode_sum.count() / count;
604     auto avg_decode = time_decode_sum.count() / count;
605     auto avg_encrypt = time_encrypt_sum.count() / count;
606     auto avg_decrypt = time_decrypt_sum.count() / count;
607     auto avg_add = time_add_sum.count() / (3 * count);
608     auto avg_multiply = time_multiply_sum.count() / count;
609     auto avg_multiply_plain = time_multiply_plain_sum.count() / count;
610     auto avg_square = time_square_sum.count() / count;
611     auto avg_relinearize = time_relinearize_sum.count() / count;
612     auto avg_rescale = time_rescale_sum.count() / count;
613     auto avg_rotate_one_step = time_rotate_one_step_sum.count() / (2 * count);
614     auto avg_rotate_random = time_rotate_random_sum.count() / count;
615     auto avg_conjugate = time_conjugate_sum.count() / count;
616     auto avg_serialize = time_serialize_sum.count() / count;
617 #ifdef SEAL_USE_ZLIB
618     auto avg_serialize_zlib = time_serialize_zlib_sum.count() / count;
619 #endif
620 #ifdef SEAL_USE_ZSTD
621     auto avg_serialize_zstd = time_serialize_zstd_sum.count() / count;
622 #endif
623     cout << "Average encode: " << avg_encode << " microseconds" << endl;
624     cout << "Average decode: " << avg_decode << " microseconds" << endl;
625     cout << "Average encrypt: " << avg_encrypt << " microseconds" << endl;
626     cout << "Average decrypt: " << avg_decrypt << " microseconds" << endl;
627     cout << "Average add: " << avg_add << " microseconds" << endl;
628     cout << "Average multiply: " << avg_multiply << " microseconds" << endl;
629     cout << "Average multiply plain: " << avg_multiply_plain << " microseconds" << endl;
630     cout << "Average square: " << avg_square << " microseconds" << endl;
631     if (context.using_keyswitching())
632     {
633         cout << "Average relinearize: " << avg_relinearize << " microseconds" << endl;
634         cout << "Average rescale: " << avg_rescale << " microseconds" << endl;
635         cout << "Average rotate vector one step: " << avg_rotate_one_step << " microseconds" << endl;
636         cout << "Average rotate vector random: " << avg_rotate_random << " microseconds" << endl;
637         cout << "Average complex conjugate: " << avg_conjugate << " microseconds" << endl;
638     }
639     cout << "Average serialize ciphertext: " << avg_serialize << " microseconds" << endl;
640 #ifdef SEAL_USE_ZLIB
641     cout << "Average compressed (ZLIB) serialize ciphertext: " << avg_serialize_zlib << " microseconds" << endl;
642 #endif
643 #ifdef SEAL_USE_ZSTD
644     cout << "Average compressed (Zstandard) serialize ciphertext: " << avg_serialize_zstd << " microseconds" << endl;
645 #endif
646     cout.flush();
647 }
648 
example_bfv_performance_default()649 void example_bfv_performance_default()
650 {
651     print_example_banner("BFV Performance Test with Degrees: 4096, 8192, and 16384");
652 
653     EncryptionParameters parms(scheme_type::bfv);
654     size_t poly_modulus_degree = 4096;
655     parms.set_poly_modulus_degree(poly_modulus_degree);
656     parms.set_coeff_modulus(CoeffModulus::BFVDefault(poly_modulus_degree));
657     parms.set_plain_modulus(786433);
658     bfv_performance_test(parms);
659 
660     cout << endl;
661     poly_modulus_degree = 8192;
662     parms.set_poly_modulus_degree(poly_modulus_degree);
663     parms.set_coeff_modulus(CoeffModulus::BFVDefault(poly_modulus_degree));
664     parms.set_plain_modulus(786433);
665     bfv_performance_test(parms);
666 
667     cout << endl;
668     poly_modulus_degree = 16384;
669     parms.set_poly_modulus_degree(poly_modulus_degree);
670     parms.set_coeff_modulus(CoeffModulus::BFVDefault(poly_modulus_degree));
671     parms.set_plain_modulus(786433);
672     bfv_performance_test(parms);
673 
674     /*
675     Comment out the following to run the biggest example.
676     */
677     // cout << endl;
678     // poly_modulus_degree = 32768;
679     // parms.set_poly_modulus_degree(poly_modulus_degree);
680     // parms.set_coeff_modulus(CoeffModulus::BFVDefault(poly_modulus_degree));
681     // parms.set_plain_modulus(786433);
682     // bfv_performance_test(parms);
683 }
684 
example_bfv_performance_custom()685 void example_bfv_performance_custom()
686 {
687     size_t poly_modulus_degree = 0;
688     cout << endl << "Set poly_modulus_degree (1024, 2048, 4096, 8192, 16384, or 32768): ";
689     if (!(cin >> poly_modulus_degree))
690     {
691         cout << "Invalid option." << endl;
692         cin.clear();
693         cin.ignore(numeric_limits<streamsize>::max(), '\n');
694         return;
695     }
696     if (poly_modulus_degree < 1024 || poly_modulus_degree > 32768 ||
697         (poly_modulus_degree & (poly_modulus_degree - 1)) != 0)
698     {
699         cout << "Invalid option." << endl;
700         return;
701     }
702 
703     string banner = "BFV Performance Test with Degree: ";
704     print_example_banner(banner + to_string(poly_modulus_degree));
705 
706     EncryptionParameters parms(scheme_type::bfv);
707     parms.set_poly_modulus_degree(poly_modulus_degree);
708     parms.set_coeff_modulus(CoeffModulus::BFVDefault(poly_modulus_degree));
709     if (poly_modulus_degree == 1024)
710     {
711         parms.set_plain_modulus(12289);
712     }
713     else
714     {
715         parms.set_plain_modulus(786433);
716     }
717     bfv_performance_test(parms);
718 }
719 
example_ckks_performance_default()720 void example_ckks_performance_default()
721 {
722     print_example_banner("CKKS Performance Test with Degrees: 4096, 8192, and 16384");
723 
724     // It is not recommended to use BFVDefault primes in CKKS. However, for performance
725     // test, BFVDefault primes are good enough.
726     EncryptionParameters parms(scheme_type::ckks);
727     size_t poly_modulus_degree = 4096;
728     parms.set_poly_modulus_degree(poly_modulus_degree);
729     parms.set_coeff_modulus(CoeffModulus::BFVDefault(poly_modulus_degree));
730     ckks_performance_test(parms);
731 
732     cout << endl;
733     poly_modulus_degree = 8192;
734     parms.set_poly_modulus_degree(poly_modulus_degree);
735     parms.set_coeff_modulus(CoeffModulus::BFVDefault(poly_modulus_degree));
736     ckks_performance_test(parms);
737 
738     cout << endl;
739     poly_modulus_degree = 16384;
740     parms.set_poly_modulus_degree(poly_modulus_degree);
741     parms.set_coeff_modulus(CoeffModulus::BFVDefault(poly_modulus_degree));
742     ckks_performance_test(parms);
743 
744     /*
745     Comment out the following to run the biggest example.
746     */
747     // cout << endl;
748     // poly_modulus_degree = 32768;
749     // parms.set_poly_modulus_degree(poly_modulus_degree);
750     // parms.set_coeff_modulus(CoeffModulus::BFVDefault(poly_modulus_degree));
751     // ckks_performance_test(parms);
752 }
753 
example_ckks_performance_custom()754 void example_ckks_performance_custom()
755 {
756     size_t poly_modulus_degree = 0;
757     cout << endl << "Set poly_modulus_degree (1024, 2048, 4096, 8192, 16384, or 32768): ";
758     if (!(cin >> poly_modulus_degree))
759     {
760         cout << "Invalid option." << endl;
761         cin.clear();
762         cin.ignore(numeric_limits<streamsize>::max(), '\n');
763         return;
764     }
765     if (poly_modulus_degree < 1024 || poly_modulus_degree > 32768 ||
766         (poly_modulus_degree & (poly_modulus_degree - 1)) != 0)
767     {
768         cout << "Invalid option." << endl;
769         return;
770     }
771 
772     string banner = "CKKS Performance Test with Degree: ";
773     print_example_banner(banner + to_string(poly_modulus_degree));
774 
775     EncryptionParameters parms(scheme_type::ckks);
776     parms.set_poly_modulus_degree(poly_modulus_degree);
777     parms.set_coeff_modulus(CoeffModulus::BFVDefault(poly_modulus_degree));
778     ckks_performance_test(parms);
779 }
780 
781 /*
782 Prints a sub-menu to select the performance test.
783 */
example_performance_test()784 void example_performance_test()
785 {
786     print_example_banner("Example: Performance Test");
787 
788     while (true)
789     {
790         cout << endl;
791         cout << "Select a scheme (and optionally poly_modulus_degree):" << endl;
792         cout << "  1. BFV with default degrees" << endl;
793         cout << "  2. BFV with a custom degree" << endl;
794         cout << "  3. CKKS with default degrees" << endl;
795         cout << "  4. CKKS with a custom degree" << endl;
796         cout << "  0. Back to main menu" << endl;
797 
798         int selection = 0;
799         cout << endl << "> Run performance test (1 ~ 4) or go back (0): ";
800         if (!(cin >> selection))
801         {
802             cout << "Invalid option." << endl;
803             cin.clear();
804             cin.ignore(numeric_limits<streamsize>::max(), '\n');
805             continue;
806         }
807 
808         switch (selection)
809         {
810         case 1:
811             example_bfv_performance_default();
812             break;
813 
814         case 2:
815             example_bfv_performance_custom();
816             break;
817 
818         case 3:
819             example_ckks_performance_default();
820             break;
821 
822         case 4:
823             example_ckks_performance_custom();
824             break;
825 
826         case 0:
827             cout << endl;
828             return;
829 
830         default:
831             cout << "Invalid option." << endl;
832         }
833     }
834 }
835