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