From a39c144e2f109d9f6b8e5309445565e6b760502c Mon Sep 17 00:00:00 2001 From: Jean-Philippe Bossuat Date: Wed, 22 Nov 2023 10:46:52 +0100 Subject: [PATCH 1/6] [schemes]: fixed benchmarks --- schemes/bfv/bfv_benchmark_test.go | 227 +++++++++++++++++------ schemes/bgv/bgv_benchmark_test.go | 256 +++++++++++++++++++------- schemes/ckks/ckks_benchmarks_test.go | 260 ++++++++++++++++++++++----- 3 files changed, 581 insertions(+), 162 deletions(-) diff --git a/schemes/bfv/bfv_benchmark_test.go b/schemes/bfv/bfv_benchmark_test.go index b48c62e74..d5e8873bb 100644 --- a/schemes/bfv/bfv_benchmark_test.go +++ b/schemes/bfv/bfv_benchmark_test.go @@ -2,32 +2,49 @@ package bfv import ( "encoding/json" + "fmt" "runtime" "testing" "github.com/tuneinsight/lattigo/v5/core/rlwe" ) +func GetBenchName(params Parameters, opname string) string { + + return fmt.Sprintf("%s/logN=%d/Qi=%d/Pi=%d/LogSlots=%d", + opname, + params.LogN(), + params.QCount(), + params.PCount(), + params.LogMaxSlots()) +} + func BenchmarkBFV(b *testing.B) { var err error - paramsLiterals := testParams - - if *flagParamString != "" { - var jsonParams ParametersLiteral - if err = json.Unmarshal([]byte(*flagParamString), &jsonParams); err != nil { + var testParams []ParametersLiteral + switch { + case *flagParamString != "": // the custom test suite reads the parameters from the -params flag + testParams = append(testParams, ParametersLiteral{}) + if err = json.Unmarshal([]byte(*flagParamString), &testParams[0]); err != nil { b.Fatal(err) } - paramsLiterals = []ParametersLiteral{jsonParams} // the custom test suite reads the parameters from the -params flag + default: + testParams = []ParametersLiteral{ + { + LogN: 14, + LogQ: []int{50, 40, 40, 40, 40, 40, 40, 40}, + LogP: []int{60}, + PlaintextModulus: 0x10001, + }, + } } - for _, p := range paramsLiterals[:] { - - p.PlaintextModulus = testPlaintextModulus[1] + for _, paramsLiteral := range testParams { var params Parameters - if params, err = NewParametersFromLiteral(p); err != nil { + if params, err = NewParametersFromLiteral(paramsLiteral); err != nil { b.Error(err) b.Fail() } @@ -65,27 +82,39 @@ func benchEncoder(tc *testContext, b *testing.B) { level := params.MaxLevel() plaintext := NewPlaintext(params, level) - b.Run(GetTestName("Encoder/Encode/Uint", params, level), func(b *testing.B) { + b.Run(GetBenchName(params, "Encoder/Encode/Uint"), func(b *testing.B) { for i := 0; i < b.N; i++ { - encoder.Encode(coeffsUint64, plaintext) + if err := encoder.Encode(coeffsUint64, plaintext); err != nil { + b.Log(err) + b.Fail() + } } }) - b.Run(GetTestName("Encoder/Encode/Int", params, level), func(b *testing.B) { + b.Run(GetBenchName(params, "Encoder/Encode/Int"), func(b *testing.B) { for i := 0; i < b.N; i++ { - encoder.Encode(coeffsInt64, plaintext) + if err := encoder.Encode(coeffsInt64, plaintext); err != nil { + b.Log(err) + b.Fail() + } } }) - b.Run(GetTestName("Encoder/Decode/Uint", params, level), func(b *testing.B) { + b.Run(GetBenchName(params, "Encoder/Decode/Uint"), func(b *testing.B) { for i := 0; i < b.N; i++ { - encoder.Decode(plaintext, coeffsUint64) + if err := encoder.Decode(plaintext, coeffsUint64); err != nil { + b.Log(err) + b.Fail() + } } }) - b.Run(GetTestName("Encoder/Decode/Int", params, level), func(b *testing.B) { + b.Run(GetBenchName(params, "Encoder/Decode/Int"), func(b *testing.B) { for i := 0; i < b.N; i++ { - encoder.Decode(plaintext, coeffsInt64) + if err := encoder.Decode(plaintext, coeffsInt64); err != nil { + b.Log(err) + b.Fail() + } } }) } @@ -94,99 +123,181 @@ func benchEvaluator(tc *testContext, b *testing.B) { params := tc.params eval := tc.evaluator - scale := rlwe.NewScale(1) - level := params.MaxLevel() - ciphertext0 := rlwe.NewCiphertextRandom(tc.prng, params.Parameters, 1, level) - ciphertext1 := rlwe.NewCiphertextRandom(tc.prng, params.Parameters, 1, level) - ct := rlwe.NewCiphertextRandom(tc.prng, params.Parameters, 0, level) - plaintext1 := &rlwe.Plaintext{Value: ct.Value[0]} - plaintext1.Element.Value = ct.Value[:1] - plaintext1.Scale = scale - plaintext1.IsNTT = ciphertext0.IsNTT + plaintext := NewPlaintext(params, params.MaxLevel()) + plaintext.Value = rlwe.NewCiphertextRandom(tc.prng, params.Parameters, 0, plaintext.Level()).Value[0] + + ciphertext1 := rlwe.NewCiphertextRandom(tc.prng, params.Parameters, 1, params.MaxLevel()) + ciphertext2 := rlwe.NewCiphertextRandom(tc.prng, params.Parameters, 1, params.MaxLevel()) scalar := params.PlaintextModulus() >> 1 - b.Run(GetTestName("Evaluator/Add/Ct/Ct", params, level), func(b *testing.B) { + *ciphertext1.MetaData = *plaintext.MetaData + *ciphertext2.MetaData = *plaintext.MetaData + + vector := plaintext.Value.Coeffs[0][:params.MaxSlots()] + + b.Run(GetBenchName(params, "Evaluator/Add/Scalar"), func(b *testing.B) { + receiver := NewCiphertext(params, 1, ciphertext1.Level()) + b.ResetTimer() for i := 0; i < b.N; i++ { - eval.Add(ciphertext0, ciphertext1, ciphertext0) + if err := eval.Add(ciphertext1, scalar, receiver); err != nil { + b.Log(err) + b.Fail() + } } }) - b.Run(GetTestName("Evaluator/Add/Ct/Pt", params, level), func(b *testing.B) { + b.Run(GetBenchName(params, "Evaluator/Add/Vector"), func(b *testing.B) { + receiver := NewCiphertext(params, 1, ciphertext1.Level()) + b.ResetTimer() + for i := 0; i < b.N; i++ { + if err := eval.Add(ciphertext1, vector, receiver); err != nil { + b.Log(err) + b.Fail() + } + } + }) + + b.Run(GetBenchName(params, "Evaluator/Add/Plaintext"), func(b *testing.B) { + receiver := NewCiphertext(params, 1, ciphertext1.Level()) + b.ResetTimer() + for i := 0; i < b.N; i++ { + if err := eval.Add(ciphertext1, plaintext, receiver); err != nil { + b.Log(err) + b.Fail() + } + } + }) + + b.Run(GetBenchName(params, "Evaluator/Add/Ciphertext"), func(b *testing.B) { + receiver := NewCiphertext(params, 1, ciphertext1.Level()) + b.ResetTimer() for i := 0; i < b.N; i++ { - eval.Add(ciphertext0, plaintext1, ciphertext0) + if err := eval.Add(ciphertext1, ciphertext2, receiver); err != nil { + b.Log(err) + b.Fail() + } } }) - b.Run(GetTestName("Evaluator/Add/Ct/Scalar", params, level), func(b *testing.B) { + b.Run(GetBenchName(params, "Evaluator/Mul/Scalar"), func(b *testing.B) { + receiver := NewCiphertext(params, 1, ciphertext1.Level()) + b.ResetTimer() for i := 0; i < b.N; i++ { - eval.Add(ciphertext0, scalar, ciphertext0) + if err := eval.Mul(ciphertext1, scalar, receiver); err != nil { + b.Log(err) + b.Fail() + } } }) - b.Run(GetTestName("Evaluator/Mul/Ct/Ct", params, level), func(b *testing.B) { - receiver := NewCiphertext(params, 2, level) + b.Run(GetBenchName(params, "Evaluator/Mul/Plaintext"), func(b *testing.B) { + receiver := NewCiphertext(params, 1, ciphertext1.Level()) + b.ResetTimer() for i := 0; i < b.N; i++ { - eval.Mul(ciphertext0, ciphertext1, receiver) + if err := eval.Mul(ciphertext1, plaintext, receiver); err != nil { + b.Log(err) + b.Fail() + } } }) - b.Run(GetTestName("Evaluator/Mul/Ct/Pt", params, level), func(b *testing.B) { + b.Run(GetBenchName(params, "Evaluator/Mul/Vector"), func(b *testing.B) { + receiver := NewCiphertext(params, 1, ciphertext1.Level()) + b.ResetTimer() for i := 0; i < b.N; i++ { - eval.Mul(ciphertext0, plaintext1, ciphertext0) + if err := eval.Mul(ciphertext1, vector, receiver); err != nil { + b.Log(err) + b.Fail() + } } }) - b.Run(GetTestName("Evaluator/Mul/Ct/Scalar", params, level), func(b *testing.B) { + b.Run(GetBenchName(params, "Evaluator/Mul/Ciphertext"), func(b *testing.B) { + receiver := NewCiphertext(params, 2, ciphertext1.Level()) + b.ResetTimer() for i := 0; i < b.N; i++ { - eval.Mul(ciphertext0, scalar, ciphertext0) + if err := eval.Mul(ciphertext1, ciphertext2, receiver); err != nil { + b.Log(err) + b.Fail() + } } }) - b.Run(GetTestName("Evaluator/Mul/Ct/Vector", params, level), func(b *testing.B) { - coeffs := plaintext1.Value.Coeffs[0][:params.MaxSlots()] + b.Run(GetBenchName(params, "Evaluator/MulRelin/Ciphertext"), func(b *testing.B) { + receiver := NewCiphertext(params, 1, ciphertext1.Level()) + b.ResetTimer() for i := 0; i < b.N; i++ { - eval.Mul(ciphertext0, coeffs, ciphertext0) + if err := eval.MulRelin(ciphertext1, ciphertext2, receiver); err != nil { + b.Log(err) + b.Fail() + } } }) - b.Run(GetTestName("Evaluator/MulRelin/Ct/Ct", params, level), func(b *testing.B) { + b.Run(GetBenchName(params, "Evaluator/MulThenAdd/Scalar"), func(b *testing.B) { + receiver := NewCiphertext(params, 1, ciphertext1.Level()) + b.ResetTimer() for i := 0; i < b.N; i++ { - eval.MulRelin(ciphertext0, ciphertext1, ciphertext0) + if err := eval.MulThenAdd(ciphertext1, scalar, receiver); err != nil { + b.Log(err) + b.Fail() + } } }) - b.Run(GetTestName("Evaluator/MulRelinThenAdd/Ct/Ct", params, level), func(b *testing.B) { - ciphertext2 := rlwe.NewCiphertextRandom(tc.prng, params.Parameters, 1, level) + b.Run(GetBenchName(params, "Evaluator/MulThenAdd/Vector"), func(b *testing.B) { + receiver := NewCiphertext(params, 1, ciphertext1.Level()) + b.ResetTimer() for i := 0; i < b.N; i++ { - eval.MulRelinThenAdd(ciphertext0, ciphertext1, ciphertext2) + if err := eval.MulThenAdd(ciphertext1, vector, receiver); err != nil { + b.Log(err) + b.Fail() + } } }) - b.Run(GetTestName("Evaluator/MulThenAdd/Ct/Pt", params, level), func(b *testing.B) { + b.Run(GetBenchName(params, "Evaluator/MulThenAdd/Plaintext"), func(b *testing.B) { + receiver := NewCiphertext(params, 1, ciphertext1.Level()) + b.ResetTimer() for i := 0; i < b.N; i++ { - eval.MulThenAdd(ciphertext0, plaintext1, ciphertext1) + if err := eval.MulThenAdd(ciphertext1, plaintext, receiver); err != nil { + b.Log(err) + b.Fail() + } } }) - b.Run(GetTestName("Evaluator/MulThenAdd/Ct/Scalar", params, level), func(b *testing.B) { + b.Run(GetBenchName(params, "Evaluator/MulThenAdd/Ciphertext"), func(b *testing.B) { + receiver := NewCiphertext(params, 1, ciphertext1.Level()) + b.ResetTimer() for i := 0; i < b.N; i++ { - eval.MulThenAdd(ciphertext0, scalar, ciphertext1) + if err := eval.MulThenAdd(ciphertext1, plaintext, receiver); err != nil { + b.Log(err) + b.Fail() + } } }) - b.Run(GetTestName("Evaluator/MulThenAdd/Ct/Vector", params, level), func(b *testing.B) { - coeffs := plaintext1.Value.Coeffs[0][:params.MaxSlots()] + b.Run(GetBenchName(params, "Evaluator/MulRelinThenAdd/Ciphertext"), func(b *testing.B) { + receiver := NewCiphertext(params, 2, ciphertext1.Level()) + b.ResetTimer() for i := 0; i < b.N; i++ { - eval.MulThenAdd(ciphertext0, coeffs, ciphertext1) + if err := eval.MulRelinThenAdd(ciphertext1, ciphertext2, receiver); err != nil { + b.Log(err) + b.Fail() + } } }) - b.Run(GetTestName("Evaluator/Rescale", params, level), func(b *testing.B) { - receiver := NewCiphertext(params, 1, level-1) + b.Run(GetBenchName(params, "Evaluator/Rotate"), func(b *testing.B) { + gk := tc.kgen.GenGaloisKeyNew(5, tc.sk) + evk := rlwe.NewMemEvaluationKeySet(nil, gk) + eval := eval.WithKey(evk) + receiver := NewCiphertext(params, 1, ciphertext2.Level()) b.ResetTimer() for i := 0; i < b.N; i++ { - if err := eval.Rescale(ciphertext0, receiver); err != nil { + if err := eval.RotateColumns(ciphertext2, 1, receiver); err != nil { b.Log(err) b.Fail() } diff --git a/schemes/bgv/bgv_benchmark_test.go b/schemes/bgv/bgv_benchmark_test.go index 32f8b04a2..899858818 100644 --- a/schemes/bgv/bgv_benchmark_test.go +++ b/schemes/bgv/bgv_benchmark_test.go @@ -2,32 +2,49 @@ package bgv import ( "encoding/json" + "fmt" "runtime" "testing" "github.com/tuneinsight/lattigo/v5/core/rlwe" ) +func GetBenchName(params Parameters, opname string) string { + + return fmt.Sprintf("%s/logN=%d/Qi=%d/Pi=%d/LogSlots=%d", + opname, + params.LogN(), + params.QCount(), + params.PCount(), + params.LogMaxSlots()) +} + func BenchmarkBGV(b *testing.B) { var err error - paramsLiterals := testParams - - if *flagParamString != "" { - var jsonParams ParametersLiteral - if err = json.Unmarshal([]byte(*flagParamString), &jsonParams); err != nil { + var testParams []ParametersLiteral + switch { + case *flagParamString != "": // the custom test suite reads the parameters from the -params flag + testParams = append(testParams, ParametersLiteral{}) + if err = json.Unmarshal([]byte(*flagParamString), &testParams[0]); err != nil { b.Fatal(err) } - paramsLiterals = []ParametersLiteral{jsonParams} // the custom test suite reads the parameters from the -params flag + default: + testParams = []ParametersLiteral{ + { + LogN: 14, + LogQ: []int{50, 40, 40, 40, 40, 40, 40, 40}, + LogP: []int{60}, + PlaintextModulus: 0x10001, + }, + } } - for _, p := range paramsLiterals[:] { - - p.PlaintextModulus = testPlaintextModulus[1] + for _, paramsLiteral := range testParams { var params Parameters - if params, err = NewParametersFromLiteral(p); err != nil { + if params, err = NewParametersFromLiteral(paramsLiteral); err != nil { b.Error(err) b.Fail() } @@ -65,27 +82,39 @@ func benchEncoder(tc *testContext, b *testing.B) { level := params.MaxLevel() plaintext := NewPlaintext(params, level) - b.Run(GetTestName("Encoder/Encode/Uint", params, level), func(b *testing.B) { + b.Run(GetBenchName(params, "Encoder/Encode/Uint"), func(b *testing.B) { for i := 0; i < b.N; i++ { - encoder.Encode(coeffsUint64, plaintext) + if err := encoder.Encode(coeffsUint64, plaintext); err != nil { + b.Log(err) + b.Fail() + } } }) - b.Run(GetTestName("Encoder/Encode/Int", params, level), func(b *testing.B) { + b.Run(GetBenchName(params, "Encoder/Encode/Int"), func(b *testing.B) { for i := 0; i < b.N; i++ { - encoder.Encode(coeffsInt64, plaintext) + if err := encoder.Encode(coeffsInt64, plaintext); err != nil { + b.Log(err) + b.Fail() + } } }) - b.Run(GetTestName("Encoder/Decode/Uint", params, level), func(b *testing.B) { + b.Run(GetBenchName(params, "Encoder/Decode/Uint"), func(b *testing.B) { for i := 0; i < b.N; i++ { - encoder.Decode(plaintext, coeffsUint64) + if err := encoder.Decode(plaintext, coeffsUint64); err != nil { + b.Log(err) + b.Fail() + } } }) - b.Run(GetTestName("Encoder/Decode/Int", params, level), func(b *testing.B) { + b.Run(GetBenchName(params, "Encoder/Decode/Int"), func(b *testing.B) { for i := 0; i < b.N; i++ { - encoder.Decode(plaintext, coeffsInt64) + if err := encoder.Decode(plaintext, coeffsInt64); err != nil { + b.Log(err) + b.Fail() + } } }) } @@ -94,111 +123,214 @@ func benchEvaluator(tc *testContext, b *testing.B) { params := tc.params eval := tc.evaluator - scale := rlwe.NewScale(1) - level := params.MaxLevel() - ciphertext0 := rlwe.NewCiphertextRandom(tc.prng, params.Parameters, 1, level) - ciphertext1 := rlwe.NewCiphertextRandom(tc.prng, params.Parameters, 1, level) - ct := rlwe.NewCiphertextRandom(tc.prng, params.Parameters, 0, level) - plaintext1 := &rlwe.Plaintext{Value: ct.Value[0]} - plaintext1.Element.Value = ct.Value[:1] - plaintext1.Scale = scale - plaintext1.IsNTT = ciphertext0.IsNTT + plaintext := NewPlaintext(params, params.MaxLevel()) + plaintext.Value = rlwe.NewCiphertextRandom(tc.prng, params.Parameters, 0, plaintext.Level()).Value[0] + + ciphertext1 := rlwe.NewCiphertextRandom(tc.prng, params.Parameters, 1, params.MaxLevel()) + ciphertext2 := rlwe.NewCiphertextRandom(tc.prng, params.Parameters, 1, params.MaxLevel()) scalar := params.PlaintextModulus() >> 1 - b.Run(GetTestName("Evaluator/Add/Ct/Ct", params, level), func(b *testing.B) { + *ciphertext1.MetaData = *plaintext.MetaData + *ciphertext2.MetaData = *plaintext.MetaData + + vector := plaintext.Value.Coeffs[0][:params.MaxSlots()] + + b.Run(GetBenchName(params, "Evaluator/Add/Scalar"), func(b *testing.B) { + receiver := NewCiphertext(params, 1, ciphertext1.Level()) + b.ResetTimer() for i := 0; i < b.N; i++ { - eval.Add(ciphertext0, ciphertext1, ciphertext0) + if err := eval.Add(ciphertext1, scalar, receiver); err != nil { + b.Log(err) + b.Fail() + } } }) - b.Run(GetTestName("Evaluator/Add/Ct/Pt", params, level), func(b *testing.B) { + b.Run(GetBenchName(params, "Evaluator/Add/Vector"), func(b *testing.B) { + receiver := NewCiphertext(params, 1, ciphertext1.Level()) + b.ResetTimer() + for i := 0; i < b.N; i++ { + if err := eval.Add(ciphertext1, vector, receiver); err != nil { + b.Log(err) + b.Fail() + } + } + }) + + b.Run(GetBenchName(params, "Evaluator/Add/Plaintext"), func(b *testing.B) { + receiver := NewCiphertext(params, 1, ciphertext1.Level()) + b.ResetTimer() + for i := 0; i < b.N; i++ { + if err := eval.Add(ciphertext1, plaintext, receiver); err != nil { + b.Log(err) + b.Fail() + } + } + }) + + b.Run(GetBenchName(params, "Evaluator/Add/Ciphertext"), func(b *testing.B) { + receiver := NewCiphertext(params, 1, ciphertext1.Level()) + b.ResetTimer() + for i := 0; i < b.N; i++ { + if err := eval.Add(ciphertext1, ciphertext2, receiver); err != nil { + b.Log(err) + b.Fail() + } + } + }) + + b.Run(GetBenchName(params, "Evaluator/Mul/Scalar"), func(b *testing.B) { + receiver := NewCiphertext(params, 1, ciphertext1.Level()) + b.ResetTimer() for i := 0; i < b.N; i++ { - eval.Add(ciphertext0, plaintext1, ciphertext0) + if err := eval.Mul(ciphertext1, scalar, receiver); err != nil { + b.Log(err) + b.Fail() + } } }) - b.Run(GetTestName("Evaluator/Add/Ct/Scalar", params, level), func(b *testing.B) { + b.Run(GetBenchName(params, "Evaluator/Mul/Plaintext"), func(b *testing.B) { + receiver := NewCiphertext(params, 1, ciphertext1.Level()) + b.ResetTimer() for i := 0; i < b.N; i++ { - eval.Add(ciphertext0, scalar, ciphertext0) + if err := eval.Mul(ciphertext1, plaintext, receiver); err != nil { + b.Log(err) + b.Fail() + } } }) - b.Run(GetTestName("Evaluator/Mul/Ct/Ct", params, level), func(b *testing.B) { - receiver := NewCiphertext(params, 2, level) + b.Run(GetBenchName(params, "Evaluator/Mul/Vector"), func(b *testing.B) { + receiver := NewCiphertext(params, 1, ciphertext1.Level()) + b.ResetTimer() for i := 0; i < b.N; i++ { - eval.Mul(ciphertext0, ciphertext1, receiver) + if err := eval.Mul(ciphertext1, vector, receiver); err != nil { + b.Log(err) + b.Fail() + } } }) - b.Run(GetTestName("Evaluator/MulInvariant/Ct/Ct", params, level), func(b *testing.B) { + b.Run(GetBenchName(params, "Evaluator/Mul/Ciphertext"), func(b *testing.B) { + receiver := NewCiphertext(params, 2, ciphertext1.Level()) + b.ResetTimer() for i := 0; i < b.N; i++ { - eval.MulScaleInvariant(ciphertext0, plaintext1.Value.Coeffs[0], ciphertext0) + if err := eval.Mul(ciphertext1, ciphertext2, receiver); err != nil { + b.Log(err) + b.Fail() + } } }) - b.Run(GetTestName("Evaluator/Mul/Ct/Pt", params, level), func(b *testing.B) { + b.Run(GetBenchName(params, "Evaluator/MulRelin/Ciphertext"), func(b *testing.B) { + receiver := NewCiphertext(params, 1, ciphertext1.Level()) + b.ResetTimer() for i := 0; i < b.N; i++ { - eval.Mul(ciphertext0, plaintext1, ciphertext0) + if err := eval.MulRelin(ciphertext1, ciphertext2, receiver); err != nil { + b.Log(err) + b.Fail() + } } }) - b.Run(GetTestName("Evaluator/Mul/Ct/Scalar", params, level), func(b *testing.B) { + b.Run(GetBenchName(params, "Evaluator/MulInvariant/Ciphertext"), func(b *testing.B) { + receiver := NewCiphertext(params, 2, ciphertext1.Level()) + b.ResetTimer() for i := 0; i < b.N; i++ { - eval.Mul(ciphertext0, scalar, ciphertext0) + if err := eval.MulScaleInvariant(ciphertext1, ciphertext2, receiver); err != nil { + b.Log(err) + b.Fail() + } } }) - b.Run(GetTestName("Evaluator/Mul/Ct/Vector", params, level), func(b *testing.B) { - coeffs := plaintext1.Value.Coeffs[0][:params.MaxSlots()] + b.Run(GetBenchName(params, "Evaluator/MulRelinInvariant/Ciphertext"), func(b *testing.B) { + receiver := NewCiphertext(params, 1, ciphertext1.Level()) + b.ResetTimer() for i := 0; i < b.N; i++ { - eval.Mul(ciphertext0, coeffs, ciphertext0) + if err := eval.MulRelinScaleInvariant(ciphertext1, ciphertext2, receiver); err != nil { + b.Log(err) + b.Fail() + } } }) - b.Run(GetTestName("Evaluator/MulRelin/Ct/Ct", params, level), func(b *testing.B) { + b.Run(GetBenchName(params, "Evaluator/MulThenAdd/Scalar"), func(b *testing.B) { + receiver := NewCiphertext(params, 1, ciphertext1.Level()) + b.ResetTimer() for i := 0; i < b.N; i++ { - eval.MulRelin(ciphertext0, ciphertext1, ciphertext0) + if err := eval.MulThenAdd(ciphertext1, scalar, receiver); err != nil { + b.Log(err) + b.Fail() + } } }) - b.Run(GetTestName("Evaluator/MulRelinInvariant/Ct/Ct", params, level), func(b *testing.B) { + b.Run(GetBenchName(params, "Evaluator/MulThenAdd/Vector"), func(b *testing.B) { + receiver := NewCiphertext(params, 1, ciphertext1.Level()) + b.ResetTimer() for i := 0; i < b.N; i++ { - eval.MulRelinScaleInvariant(ciphertext0, ciphertext1, ciphertext0) + if err := eval.MulThenAdd(ciphertext1, vector, receiver); err != nil { + b.Log(err) + b.Fail() + } } }) - b.Run(GetTestName("Evaluator/MulRelinThenAdd/Ct/Ct", params, level), func(b *testing.B) { - ciphertext2 := rlwe.NewCiphertextRandom(tc.prng, params.Parameters, 1, level) + b.Run(GetBenchName(params, "Evaluator/MulThenAdd/Plaintext"), func(b *testing.B) { + receiver := NewCiphertext(params, 1, ciphertext1.Level()) + b.ResetTimer() for i := 0; i < b.N; i++ { - eval.MulRelinThenAdd(ciphertext0, ciphertext1, ciphertext2) + if err := eval.MulThenAdd(ciphertext1, plaintext, receiver); err != nil { + b.Log(err) + b.Fail() + } } }) - b.Run(GetTestName("Evaluator/MulThenAdd/Ct/Pt", params, level), func(b *testing.B) { + b.Run(GetBenchName(params, "Evaluator/MulThenAdd/Ciphertext"), func(b *testing.B) { + receiver := NewCiphertext(params, 1, ciphertext1.Level()) + b.ResetTimer() for i := 0; i < b.N; i++ { - eval.MulThenAdd(ciphertext0, plaintext1, ciphertext1) + if err := eval.MulThenAdd(ciphertext1, plaintext, receiver); err != nil { + b.Log(err) + b.Fail() + } } }) - b.Run(GetTestName("Evaluator/MulThenAdd/Ct/Scalar", params, level), func(b *testing.B) { + b.Run(GetBenchName(params, "Evaluator/MulRelinThenAdd/Ciphertext"), func(b *testing.B) { + receiver := NewCiphertext(params, 2, ciphertext1.Level()) + b.ResetTimer() for i := 0; i < b.N; i++ { - eval.MulThenAdd(ciphertext0, scalar, ciphertext1) + if err := eval.MulRelinThenAdd(ciphertext1, ciphertext2, receiver); err != nil { + b.Log(err) + b.Fail() + } } }) - b.Run(GetTestName("Evaluator/MulThenAdd/Ct/Vector", params, level), func(b *testing.B) { - coeffs := plaintext1.Value.Coeffs[0][:params.MaxSlots()] + b.Run(GetBenchName(params, "Evaluator/Rescale"), func(b *testing.B) { + receiver := NewCiphertext(params, 1, ciphertext1.Level()-1) + b.ResetTimer() for i := 0; i < b.N; i++ { - eval.MulThenAdd(ciphertext0, coeffs, ciphertext1) + if err := eval.Rescale(ciphertext1, receiver); err != nil { + b.Log(err) + b.Fail() + } } }) - b.Run(GetTestName("Evaluator/Rescale", params, level), func(b *testing.B) { - receiver := NewCiphertext(params, 1, level-1) + b.Run(GetBenchName(params, "Evaluator/Rotate"), func(b *testing.B) { + gk := tc.kgen.GenGaloisKeyNew(5, tc.sk) + evk := rlwe.NewMemEvaluationKeySet(nil, gk) + eval := eval.WithKey(evk) + receiver := NewCiphertext(params, 1, ciphertext2.Level()) b.ResetTimer() for i := 0; i < b.N; i++ { - if err := eval.Rescale(ciphertext0, receiver); err != nil { + if err := eval.RotateColumns(ciphertext2, 1, receiver); err != nil { b.Log(err) b.Fail() } diff --git a/schemes/ckks/ckks_benchmarks_test.go b/schemes/ckks/ckks_benchmarks_test.go index ac6fe148e..f65b0f71d 100644 --- a/schemes/ckks/ckks_benchmarks_test.go +++ b/schemes/ckks/ckks_benchmarks_test.go @@ -2,6 +2,8 @@ package ckks import ( "encoding/json" + "fmt" + "runtime" "testing" "github.com/tuneinsight/lattigo/v5/core/rlwe" @@ -9,7 +11,27 @@ import ( "github.com/tuneinsight/lattigo/v5/utils/sampling" ) -func BenchmarkCKKSScheme(b *testing.B) { +func GetBenchName(params Parameters, opname string) string { + + var PrecisionMod string + switch params.precisionMode { + case PREC64: + PrecisionMod = "PREC64" + case PREC128: + PrecisionMod = "PREC128" + } + + return fmt.Sprintf("%s/RingType=%s/logN=%d/Qi=%d/Pi=%d/LogSlots=%d/%s", + opname, + params.RingType(), + params.LogN(), + params.QCount(), + params.PCount(), + params.LogMaxSlots(), + PrecisionMod) +} + +func BenchmarkCKKS(b *testing.B) { var err error @@ -21,27 +43,36 @@ func BenchmarkCKKSScheme(b *testing.B) { b.Fatal(err) } default: - testParams = testParamsLiteral + testParams = []ParametersLiteral{ + { + LogN: 14, + LogQ: []int{50, 40, 40, 40, 40, 40, 40, 40}, + LogP: []int{60}, + LogDefaultScale: 40, + RingType: ring.Standard, + }, + } } - for _, ringType := range []ring.Type{ring.Standard, ring.ConjugateInvariant} { + for _, paramsLiteral := range testParams { - for _, paramsLiteral := range testParams { - - paramsLiteral.RingType = ringType - - var params Parameters - if params, err = NewParametersFromLiteral(paramsLiteral); err != nil { - b.Fatal(err) - } + var params Parameters + if params, err = NewParametersFromLiteral(paramsLiteral); err != nil { + b.Error(err) + b.Fail() + } - var tc *testContext - if tc, err = genTestParams(params); err != nil { - b.Fatal(err) - } + var tc *testContext + if tc, err = genTestParams(params); err != nil { + b.Fatal(err) + } - benchEncoder(tc, b) - benchEvaluator(tc, b) + for _, testSet := range []func(tc *testContext, b *testing.B){ + benchEncoder, + benchEvaluator, + } { + testSet(tc, b) + runtime.GC() } } } @@ -50,7 +81,7 @@ func benchEncoder(tc *testContext, b *testing.B) { encoder := tc.encoder - b.Run(GetTestName(tc.params, "Encoder/Encode"), func(b *testing.B) { + b.Run(GetBenchName(tc.params, "Encoder/Encode"), func(b *testing.B) { pt := NewPlaintext(tc.params, tc.params.MaxLevel()) @@ -62,11 +93,14 @@ func benchEncoder(tc *testContext, b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { - encoder.Encode(values, pt) + if err := encoder.Encode(values, pt); err != nil { + b.Log(err) + b.Fail() + } } }) - b.Run(GetTestName(tc.params, "Encoder/Decode"), func(b *testing.B) { + b.Run(GetBenchName(tc.params, "Encoder/Decode"), func(b *testing.B) { pt := NewPlaintext(tc.params, tc.params.MaxLevel()) @@ -80,67 +114,209 @@ func benchEncoder(tc *testContext, b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { - encoder.Decode(pt, values) + if err := encoder.Decode(pt, values); err != nil { + b.Log(err) + b.Fail() + } } }) } func benchEvaluator(tc *testContext, b *testing.B) { - plaintext := NewPlaintext(tc.params, tc.params.MaxLevel()) - ciphertext1 := rlwe.NewCiphertextRandom(tc.prng, tc.params.Parameters, 1, tc.params.MaxLevel()) - ciphertext2 := rlwe.NewCiphertextRandom(tc.prng, tc.params.Parameters, 1, tc.params.MaxLevel()) - receiver := rlwe.NewCiphertextRandom(tc.prng, tc.params.Parameters, 2, tc.params.MaxLevel()) + params := tc.params + plaintext := NewPlaintext(params, params.MaxLevel()) + plaintext.Value = rlwe.NewCiphertextRandom(tc.prng, params.Parameters, 0, plaintext.Level()).Value[0] + + vector := make([]float64, params.MaxSlots()) + for i := range vector { + vector[i] = 1 + } + + ciphertext1 := rlwe.NewCiphertextRandom(tc.prng, params.Parameters, 1, params.MaxLevel()) + ciphertext2 := rlwe.NewCiphertextRandom(tc.prng, params.Parameters, 1, params.MaxLevel()) + + *ciphertext1.MetaData = *plaintext.MetaData + *ciphertext2.MetaData = *plaintext.MetaData eval := tc.evaluator.WithKey(rlwe.NewMemEvaluationKeySet(tc.kgen.GenRelinearizationKeyNew(tc.sk))) - b.Run(GetTestName(tc.params, "Evaluator/Add/Scalar"), func(b *testing.B) { + b.Run(GetBenchName(params, "Evaluator/Add/Scalar"), func(b *testing.B) { + receiver := NewCiphertext(params, 1, ciphertext1.Level()) + b.ResetTimer() + for i := 0; i < b.N; i++ { + if err := eval.Add(ciphertext1, 3.1415-1.4142i, receiver); err != nil { + b.Log(err) + b.Fail() + } + } + }) + + b.Run(GetBenchName(params, "Evaluator/Add/Vector"), func(b *testing.B) { + receiver := NewCiphertext(params, 1, ciphertext1.Level()) + b.ResetTimer() + for i := 0; i < b.N; i++ { + if err := eval.Add(ciphertext1, vector, receiver); err != nil { + b.Log(err) + b.Fail() + } + } + }) + + b.Run(GetBenchName(params, "Evaluator/Add/Plaintext"), func(b *testing.B) { + receiver := NewCiphertext(params, 1, ciphertext1.Level()) + b.ResetTimer() + for i := 0; i < b.N; i++ { + if err := eval.Add(ciphertext1, plaintext, receiver); err != nil { + b.Log(err) + b.Fail() + } + } + }) + + b.Run(GetBenchName(params, "Evaluator/Add/Ciphertext"), func(b *testing.B) { + receiver := NewCiphertext(params, 1, ciphertext1.Level()) + b.ResetTimer() + for i := 0; i < b.N; i++ { + if err := eval.Add(ciphertext1, ciphertext2, receiver); err != nil { + b.Log(err) + b.Fail() + } + } + }) + + b.Run(GetBenchName(params, "Evaluator/Mul/Scalar"), func(b *testing.B) { + receiver := NewCiphertext(params, 1, ciphertext1.Level()) + b.ResetTimer() for i := 0; i < b.N; i++ { - eval.Add(ciphertext1, 3.1415-1.4142i, ciphertext1) + if err := eval.Mul(ciphertext1, 3.1415-1.4142i, receiver); err != nil { + b.Log(err) + b.Fail() + } } }) - b.Run(GetTestName(tc.params, "Evaluator/Add/Pt"), func(b *testing.B) { + b.Run(GetBenchName(params, "Evaluator/Mul/Vector"), func(b *testing.B) { + receiver := NewCiphertext(params, 1, ciphertext1.Level()) + b.ResetTimer() for i := 0; i < b.N; i++ { - eval.Add(ciphertext1, plaintext, ciphertext1) + if err := eval.Mul(ciphertext1, vector, receiver); err != nil { + b.Log(err) + b.Fail() + } } }) - b.Run(GetTestName(tc.params, "Evaluator/Add/Ct"), func(b *testing.B) { + b.Run(GetBenchName(params, "Evaluator/Mul/Plaintext"), func(b *testing.B) { + receiver := NewCiphertext(params, 1, ciphertext1.Level()) + b.ResetTimer() for i := 0; i < b.N; i++ { - eval.Add(ciphertext1, ciphertext2, ciphertext1) + if err := eval.Mul(ciphertext1, plaintext, receiver); err != nil { + b.Log(err) + b.Fail() + } } }) - b.Run(GetTestName(tc.params, "Evaluator/Mul/Scalar"), func(b *testing.B) { + b.Run(GetBenchName(params, "Evaluator/Mul/Ciphertext"), func(b *testing.B) { + receiver := NewCiphertext(params, 2, ciphertext1.Level()) + b.ResetTimer() for i := 0; i < b.N; i++ { - eval.Mul(ciphertext1, 3.1415-1.4142i, ciphertext1) + if err := eval.Mul(ciphertext1, ciphertext2, receiver); err != nil { + b.Log(err) + b.Fail() + } } }) - b.Run(GetTestName(tc.params, "Evaluator/Mul/Pt"), func(b *testing.B) { + b.Run(GetBenchName(params, "Evaluator/MulRelin/Ciphertext"), func(b *testing.B) { + receiver := NewCiphertext(params, 1, ciphertext1.Level()) + b.ResetTimer() for i := 0; i < b.N; i++ { - eval.Mul(ciphertext1, plaintext, ciphertext1) + if err := eval.MulRelin(ciphertext1, ciphertext2, receiver); err != nil { + b.Log(err) + b.Fail() + } } }) - b.Run(GetTestName(tc.params, "Evaluator/Mul/Ct"), func(b *testing.B) { + b.Run(GetBenchName(params, "Evaluator/MulThenAdd/Scalar"), func(b *testing.B) { + receiver := NewCiphertext(params, 1, ciphertext1.Level()) + b.ResetTimer() for i := 0; i < b.N; i++ { - eval.Mul(ciphertext1, ciphertext2, receiver) + if err := eval.MulThenAdd(ciphertext1, 3.1415-1.4142i, receiver); err != nil { + b.Log(err) + b.Fail() + } } }) - b.Run(GetTestName(tc.params, "Evaluator/Square"), func(b *testing.B) { + b.Run(GetBenchName(params, "Evaluator/MulThenAdd/Vector"), func(b *testing.B) { + receiver := NewCiphertext(params, 1, ciphertext1.Level()) + b.ResetTimer() for i := 0; i < b.N; i++ { - eval.Mul(ciphertext1, ciphertext1, receiver) + if err := eval.MulThenAdd(ciphertext1, vector, receiver); err != nil { + b.Log(err) + b.Fail() + } } }) - b.Run(GetTestName(tc.params, "Evaluator/Rescale"), func(b *testing.B) { - ciphertext1.Scale = tc.params.DefaultScale().Mul(tc.params.DefaultScale()) + b.Run(GetBenchName(params, "Evaluator/MulThenAdd/Plaintext"), func(b *testing.B) { + receiver := NewCiphertext(params, 1, ciphertext1.Level()) + b.ResetTimer() + for i := 0; i < b.N; i++ { + if err := eval.MulThenAdd(ciphertext1, plaintext, receiver); err != nil { + b.Log(err) + b.Fail() + } + } + }) + b.Run(GetBenchName(params, "Evaluator/MulThenAdd/Ciphertext"), func(b *testing.B) { + receiver := NewCiphertext(params, 2, ciphertext1.Level()) + b.ResetTimer() + for i := 0; i < b.N; i++ { + if err := eval.MulThenAdd(ciphertext1, ciphertext2, receiver); err != nil { + b.Log(err) + b.Fail() + } + } + }) + + b.Run(GetBenchName(params, "Evaluator/MulRelinThenAdd/Ciphertext"), func(b *testing.B) { + receiver := NewCiphertext(params, 1, ciphertext1.Level()) + b.ResetTimer() + for i := 0; i < b.N; i++ { + if err := eval.MulRelinThenAdd(ciphertext1, ciphertext2, receiver); err != nil { + b.Log(err) + b.Fail() + } + } + }) + + b.Run(GetBenchName(params, "Evaluator/Rescale"), func(b *testing.B) { + receiver := NewCiphertext(params, 1, ciphertext1.Level()-1) + b.ResetTimer() + for i := 0; i < b.N; i++ { + if err := eval.Rescale(ciphertext1, receiver); err != nil { + b.Log(err) + b.Fail() + } + } + }) + + b.Run(GetBenchName(params, "Evaluator/Rotate"), func(b *testing.B) { + gk := tc.kgen.GenGaloisKeyNew(5, tc.sk) + evk := rlwe.NewMemEvaluationKeySet(nil, gk) + eval := eval.WithKey(evk) + receiver := NewCiphertext(params, 1, ciphertext1.Level()) + b.ResetTimer() for i := 0; i < b.N; i++ { - eval.RescaleTo(ciphertext1, tc.params.DefaultScale(), ciphertext2) + if err := eval.Rotate(ciphertext1, 1, receiver); err != nil { + b.Log(err) + b.Fail() + } } }) } From 8fd83985a8f473c54cf3499195a00f8bd3caf853 Mon Sep 17 00:00:00 2001 From: Jean-Philippe Bossuat Date: Thu, 23 Nov 2023 09:37:27 +0100 Subject: [PATCH 2/6] [ring]: NTT benchmarks --- ring/ntt_benchmark_test.go | 65 +++++++++++++++++++++++++++++++++++++ ring/ring_benchmark_test.go | 32 ------------------ 2 files changed, 65 insertions(+), 32 deletions(-) create mode 100644 ring/ntt_benchmark_test.go diff --git a/ring/ntt_benchmark_test.go b/ring/ntt_benchmark_test.go new file mode 100644 index 000000000..afad8dd19 --- /dev/null +++ b/ring/ntt_benchmark_test.go @@ -0,0 +1,65 @@ +package ring + +import ( + "fmt" + "testing" + + "github.com/tuneinsight/lattigo/v5/utils/sampling" +) + +func BenchmarkNTT(b *testing.B) { + benchNTT(10, 1, b) + benchNTT(11, 1, b) + benchNTT(12, 1, b) + benchNTT(13, 1, b) + benchNTT(14, 1, b) + benchNTT(15, 1, b) + benchNTT(16, 1, b) + benchINTT(10, 1, b) + benchINTT(11, 1, b) + benchINTT(12, 1, b) + benchINTT(13, 1, b) + benchINTT(14, 1, b) + benchINTT(15, 1, b) + benchINTT(16, 1, b) +} + +func benchNTT(LogN, Qi int, b *testing.B) { + b.Run(fmt.Sprintf("Forward/N=%d/Qi=%d", 1< Date: Thu, 23 Nov 2023 09:52:00 +0100 Subject: [PATCH 3/6] [he]: benchmarks for basic ops --- he/hefloat/hefloat_benchmark_test.go | 324 +++++++++++++++++++++++++ he/heint/heint_benchmark_test.go | 343 +++++++++++++++++++++++++++ schemes/bgv/bgv_benchmark_test.go | 12 +- 3 files changed, 675 insertions(+), 4 deletions(-) create mode 100644 he/hefloat/hefloat_benchmark_test.go create mode 100644 he/heint/heint_benchmark_test.go diff --git a/he/hefloat/hefloat_benchmark_test.go b/he/hefloat/hefloat_benchmark_test.go new file mode 100644 index 000000000..b41eeb2f5 --- /dev/null +++ b/he/hefloat/hefloat_benchmark_test.go @@ -0,0 +1,324 @@ +package hefloat_test + +import ( + "encoding/json" + "fmt" + "runtime" + "testing" + + "github.com/tuneinsight/lattigo/v5/core/rlwe" + "github.com/tuneinsight/lattigo/v5/he/hefloat" + "github.com/tuneinsight/lattigo/v5/ring" + "github.com/tuneinsight/lattigo/v5/schemes/ckks" + "github.com/tuneinsight/lattigo/v5/utils/sampling" +) + +func GetBenchName(params hefloat.Parameters, opname string) string { + + var PrecisionMod string + switch params.PrecisionMode() { + case ckks.PREC64: + PrecisionMod = "PREC64" + case ckks.PREC128: + PrecisionMod = "PREC128" + } + + return fmt.Sprintf("%s/RingType=%s/logN=%d/Qi=%d/Pi=%d/LogSlots=%d/%s", + opname, + params.RingType(), + params.LogN(), + params.QCount(), + params.PCount(), + params.LogMaxSlots(), + PrecisionMod) +} + +func BenchmarkHEFloat(b *testing.B) { + + var err error + + var testParams []hefloat.ParametersLiteral + switch { + case *flagParamString != "": // the custom test suite reads the parameters from the -params flag + testParams = append(testParams, hefloat.ParametersLiteral{}) + if err = json.Unmarshal([]byte(*flagParamString), &testParams[0]); err != nil { + b.Fatal(err) + } + default: + testParams = []hefloat.ParametersLiteral{ + { + LogN: 14, + LogQ: []int{50, 40, 40, 40, 40, 40, 40, 40}, + LogP: []int{60}, + LogDefaultScale: 40, + RingType: ring.Standard, + }, + } + } + + for _, paramsLiteral := range testParams { + + var params hefloat.Parameters + if params, err = hefloat.NewParametersFromLiteral(paramsLiteral); err != nil { + b.Error(err) + b.Fail() + } + + var tc *testContext + if tc, err = genTestParams(params); err != nil { + b.Fatal(err) + } + + for _, testSet := range []func(tc *testContext, b *testing.B){ + benchEncoder, + benchEvaluator, + } { + testSet(tc, b) + runtime.GC() + } + } +} + +func benchEncoder(tc *testContext, b *testing.B) { + + encoder := tc.encoder + + b.Run(GetBenchName(tc.params, "Encoder/Encode"), func(b *testing.B) { + + pt := hefloat.NewPlaintext(tc.params, tc.params.MaxLevel()) + + values := make([]complex128, 1<> 1 + + *ciphertext1.MetaData = *plaintext.MetaData + *ciphertext2.MetaData = *plaintext.MetaData + + vector := plaintext.Value.Coeffs[0][:params.MaxSlots()] + + b.Run(GetBenchName(params, "Evaluator/Add/Scalar"), func(b *testing.B) { + receiver := heint.NewCiphertext(params, 1, ciphertext1.Level()) + b.ResetTimer() + for i := 0; i < b.N; i++ { + if err := eval.Add(ciphertext1, scalar, receiver); err != nil { + b.Log(err) + b.Fail() + } + } + }) + + b.Run(GetBenchName(params, "Evaluator/Add/Vector"), func(b *testing.B) { + receiver := heint.NewCiphertext(params, 1, ciphertext1.Level()) + b.ResetTimer() + for i := 0; i < b.N; i++ { + if err := eval.Add(ciphertext1, vector, receiver); err != nil { + b.Log(err) + b.Fail() + } + } + }) + + b.Run(GetBenchName(params, "Evaluator/Add/Plaintext"), func(b *testing.B) { + receiver := heint.NewCiphertext(params, 1, ciphertext1.Level()) + b.ResetTimer() + for i := 0; i < b.N; i++ { + if err := eval.Add(ciphertext1, plaintext, receiver); err != nil { + b.Log(err) + b.Fail() + } + } + }) + + b.Run(GetBenchName(params, "Evaluator/Add/Ciphertext"), func(b *testing.B) { + receiver := heint.NewCiphertext(params, 1, ciphertext1.Level()) + b.ResetTimer() + for i := 0; i < b.N; i++ { + if err := eval.Add(ciphertext1, ciphertext2, receiver); err != nil { + b.Log(err) + b.Fail() + } + } + }) + + b.Run(GetBenchName(params, "Evaluator/Mul/Scalar"), func(b *testing.B) { + receiver := heint.NewCiphertext(params, 1, ciphertext1.Level()) + b.ResetTimer() + for i := 0; i < b.N; i++ { + if err := eval.Mul(ciphertext1, scalar, receiver); err != nil { + b.Log(err) + b.Fail() + } + } + }) + + b.Run(GetBenchName(params, "Evaluator/Mul/Plaintext"), func(b *testing.B) { + receiver := heint.NewCiphertext(params, 1, ciphertext1.Level()) + b.ResetTimer() + for i := 0; i < b.N; i++ { + if err := eval.Mul(ciphertext1, plaintext, receiver); err != nil { + b.Log(err) + b.Fail() + } + } + }) + + b.Run(GetBenchName(params, "Evaluator/Mul/Vector"), func(b *testing.B) { + receiver := heint.NewCiphertext(params, 1, ciphertext1.Level()) + b.ResetTimer() + for i := 0; i < b.N; i++ { + if err := eval.Mul(ciphertext1, vector, receiver); err != nil { + b.Log(err) + b.Fail() + } + } + }) + + b.Run(GetBenchName(params, "Evaluator/Mul/Ciphertext"), func(b *testing.B) { + receiver := heint.NewCiphertext(params, 2, ciphertext1.Level()) + b.ResetTimer() + for i := 0; i < b.N; i++ { + if err := eval.Mul(ciphertext1, ciphertext2, receiver); err != nil { + b.Log(err) + b.Fail() + } + } + }) + + b.Run(GetBenchName(params, "Evaluator/MulRelin/Ciphertext"), func(b *testing.B) { + receiver := heint.NewCiphertext(params, 1, ciphertext1.Level()) + b.ResetTimer() + for i := 0; i < b.N; i++ { + if err := eval.MulRelin(ciphertext1, ciphertext2, receiver); err != nil { + b.Log(err) + b.Fail() + } + } + }) + + b.Run(GetBenchName(params, "Evaluator/MulInvariant/Ciphertext"), func(b *testing.B) { + receiver := heint.NewCiphertext(params, 2, ciphertext1.Level()) + b.ResetTimer() + for i := 0; i < b.N; i++ { + if err := eval.MulScaleInvariant(ciphertext1, ciphertext2, receiver); err != nil { + b.Log(err) + b.Fail() + } + } + }) + + b.Run(GetBenchName(params, "Evaluator/MulRelinInvariant/Ciphertext"), func(b *testing.B) { + receiver := heint.NewCiphertext(params, 1, ciphertext1.Level()) + b.ResetTimer() + for i := 0; i < b.N; i++ { + if err := eval.MulRelinScaleInvariant(ciphertext1, ciphertext2, receiver); err != nil { + b.Log(err) + b.Fail() + } + } + }) + + b.Run(GetBenchName(params, "Evaluator/MulThenAdd/Scalar"), func(b *testing.B) { + receiver := heint.NewCiphertext(params, 1, ciphertext1.Level()) + b.ResetTimer() + for i := 0; i < b.N; i++ { + if err := eval.MulThenAdd(ciphertext1, scalar, receiver); err != nil { + b.Log(err) + b.Fail() + } + } + }) + + b.Run(GetBenchName(params, "Evaluator/MulThenAdd/Vector"), func(b *testing.B) { + receiver := heint.NewCiphertext(params, 1, ciphertext1.Level()) + b.ResetTimer() + for i := 0; i < b.N; i++ { + if err := eval.MulThenAdd(ciphertext1, vector, receiver); err != nil { + b.Log(err) + b.Fail() + } + } + }) + + b.Run(GetBenchName(params, "Evaluator/MulThenAdd/Plaintext"), func(b *testing.B) { + receiver := heint.NewCiphertext(params, 1, ciphertext1.Level()) + b.ResetTimer() + for i := 0; i < b.N; i++ { + if err := eval.MulThenAdd(ciphertext1, plaintext, receiver); err != nil { + b.Log(err) + b.Fail() + } + } + }) + + b.Run(GetBenchName(params, "Evaluator/MulThenAdd/Ciphertext"), func(b *testing.B) { + receiver := heint.NewCiphertext(params, 1, ciphertext1.Level()) + b.ResetTimer() + for i := 0; i < b.N; i++ { + if err := eval.MulThenAdd(ciphertext1, plaintext, receiver); err != nil { + b.Log(err) + b.Fail() + } + } + }) + + b.Run(GetBenchName(params, "Evaluator/MulRelinThenAdd/Ciphertext"), func(b *testing.B) { + receiver := heint.NewCiphertext(params, 2, ciphertext1.Level()) + b.ResetTimer() + for i := 0; i < b.N; i++ { + if err := eval.MulRelinThenAdd(ciphertext1, ciphertext2, receiver); err != nil { + b.Log(err) + b.Fail() + } + } + }) + + b.Run(GetBenchName(params, "Evaluator/Rescale"), func(b *testing.B) { + receiver := heint.NewCiphertext(params, 1, ciphertext1.Level()-1) + b.ResetTimer() + for i := 0; i < b.N; i++ { + if err := eval.Rescale(ciphertext1, receiver); err != nil { + b.Log(err) + b.Fail() + } + } + }) + + b.Run(GetBenchName(params, "Evaluator/Rotate"), func(b *testing.B) { + gk := tc.kgen.GenGaloisKeyNew(5, tc.sk) + evk := rlwe.NewMemEvaluationKeySet(nil, gk) + eval := eval.WithKey(evk) + receiver := heint.NewCiphertext(params, 1, ciphertext2.Level()) + b.ResetTimer() + for i := 0; i < b.N; i++ { + if err := eval.RotateColumns(ciphertext2, 1, receiver); err != nil { + b.Log(err) + b.Fail() + } + } + }) +} diff --git a/schemes/bgv/bgv_benchmark_test.go b/schemes/bgv/bgv_benchmark_test.go index 899858818..ff25f2477 100644 --- a/schemes/bgv/bgv_benchmark_test.go +++ b/schemes/bgv/bgv_benchmark_test.go @@ -10,7 +10,6 @@ import ( ) func GetBenchName(params Parameters, opname string) string { - return fmt.Sprintf("%s/logN=%d/Qi=%d/Pi=%d/LogSlots=%d", opname, params.LogN(), @@ -79,10 +78,9 @@ func benchEncoder(tc *testContext, b *testing.B) { encoder := tc.encoder - level := params.MaxLevel() - plaintext := NewPlaintext(params, level) - b.Run(GetBenchName(params, "Encoder/Encode/Uint"), func(b *testing.B) { + plaintext := NewPlaintext(params, params.MaxLevel()) + b.ResetTimer() for i := 0; i < b.N; i++ { if err := encoder.Encode(coeffsUint64, plaintext); err != nil { b.Log(err) @@ -92,6 +90,8 @@ func benchEncoder(tc *testContext, b *testing.B) { }) b.Run(GetBenchName(params, "Encoder/Encode/Int"), func(b *testing.B) { + plaintext := NewPlaintext(params, params.MaxLevel()) + b.ResetTimer() for i := 0; i < b.N; i++ { if err := encoder.Encode(coeffsInt64, plaintext); err != nil { b.Log(err) @@ -101,6 +101,8 @@ func benchEncoder(tc *testContext, b *testing.B) { }) b.Run(GetBenchName(params, "Encoder/Decode/Uint"), func(b *testing.B) { + plaintext := NewPlaintext(params, params.MaxLevel()) + b.ResetTimer() for i := 0; i < b.N; i++ { if err := encoder.Decode(plaintext, coeffsUint64); err != nil { b.Log(err) @@ -110,6 +112,8 @@ func benchEncoder(tc *testContext, b *testing.B) { }) b.Run(GetBenchName(params, "Encoder/Decode/Int"), func(b *testing.B) { + plaintext := NewPlaintext(params, params.MaxLevel()) + b.ResetTimer() for i := 0; i < b.N; i++ { if err := encoder.Decode(plaintext, coeffsInt64); err != nil { b.Log(err) From 493eb8b318bb0d90e531a7abd3748761dd31282a Mon Sep 17 00:00:00 2001 From: Jean-Philippe Bossuat Date: Thu, 23 Nov 2023 11:33:13 +0100 Subject: [PATCH 4/6] [hefloat]: added more benchmarks --- he/hefloat/hefloat_benchmark_test.go | 114 +++++++++++++++++++++++++++ 1 file changed, 114 insertions(+) diff --git a/he/hefloat/hefloat_benchmark_test.go b/he/hefloat/hefloat_benchmark_test.go index b41eeb2f5..5afee7020 100644 --- a/he/hefloat/hefloat_benchmark_test.go +++ b/he/hefloat/hefloat_benchmark_test.go @@ -70,7 +70,9 @@ func BenchmarkHEFloat(b *testing.B) { } for _, testSet := range []func(tc *testContext, b *testing.B){ + benchKeyGenerator, benchEncoder, + benchEncryptor, benchEvaluator, } { testSet(tc, b) @@ -79,6 +81,40 @@ func BenchmarkHEFloat(b *testing.B) { } } +func benchKeyGenerator(tc *testContext, b *testing.B) { + + params := tc.params + + b.Run(GetBenchName(params, "KeyGenerator/GenSecretKey"), func(b *testing.B) { + sk := rlwe.NewSecretKey(params) + kgen := tc.kgen + b.ResetTimer() + for i := 0; i < b.N; i++ { + kgen.GenSecretKey(sk) + } + }) + + b.Run(GetBenchName(params, "KeyGenerator/GenPublicKey"), func(b *testing.B) { + sk := tc.sk + pk := rlwe.NewPublicKey(params) + kgen := tc.kgen + b.ResetTimer() + for i := 0; i < b.N; i++ { + kgen.GenPublicKey(sk, pk) + } + }) + + b.Run(GetBenchName(params, "KeyGenerator/GenEvaluationKey"), func(b *testing.B) { + sk := tc.sk + kgen := tc.kgen + evk := rlwe.NewEvaluationKey(params) + b.ResetTimer() + for i := 0; i < b.N; i++ { + kgen.GenEvaluationKey(sk, sk, evk) + } + }) +} + func benchEncoder(tc *testContext, b *testing.B) { encoder := tc.encoder @@ -124,6 +160,84 @@ func benchEncoder(tc *testContext, b *testing.B) { }) } +func benchEncryptor(tc *testContext, b *testing.B) { + + params := tc.params + + b.Run(GetBenchName(params, "Encryptor/Encrypt/Sk"), func(b *testing.B) { + + pt := hefloat.NewPlaintext(params, params.MaxLevel()) + + values := make([]complex128, 1< Date: Thu, 23 Nov 2023 11:33:23 +0100 Subject: [PATCH 5/6] [heint]: added benchmarks --- he/heint/heint_benchmark_test.go | 110 +++++++++++++++++++++++++++++++ 1 file changed, 110 insertions(+) diff --git a/he/heint/heint_benchmark_test.go b/he/heint/heint_benchmark_test.go index 1137881c9..fcdf5c9ab 100644 --- a/he/heint/heint_benchmark_test.go +++ b/he/heint/heint_benchmark_test.go @@ -55,7 +55,9 @@ func BenchmarkHEInt(b *testing.B) { } for _, testSet := range []func(tc *testContext, b *testing.B){ + benchKeyGenerator, benchEncoder, + benchEncryptor, benchEvaluator, } { testSet(tc, b) @@ -64,6 +66,40 @@ func BenchmarkHEInt(b *testing.B) { } } +func benchKeyGenerator(tc *testContext, b *testing.B) { + + params := tc.params + + b.Run(GetBenchName(params, "KeyGenerator/GenSecretKey"), func(b *testing.B) { + sk := rlwe.NewSecretKey(params) + kgen := tc.kgen + b.ResetTimer() + for i := 0; i < b.N; i++ { + kgen.GenSecretKey(sk) + } + }) + + b.Run(GetBenchName(params, "KeyGenerator/GenPublicKey"), func(b *testing.B) { + sk := tc.sk + pk := rlwe.NewPublicKey(params) + kgen := tc.kgen + b.ResetTimer() + for i := 0; i < b.N; i++ { + kgen.GenPublicKey(sk, pk) + } + }) + + b.Run(GetBenchName(params, "KeyGenerator/GenEvaluationKey"), func(b *testing.B) { + sk := tc.sk + kgen := tc.kgen + evk := rlwe.NewEvaluationKey(params) + b.ResetTimer() + for i := 0; i < b.N; i++ { + kgen.GenEvaluationKey(sk, sk, evk) + } + }) +} + func benchEncoder(tc *testContext, b *testing.B) { params := tc.params @@ -123,6 +159,80 @@ func benchEncoder(tc *testContext, b *testing.B) { }) } +func benchEncryptor(tc *testContext, b *testing.B) { + + params := tc.params + + b.Run(GetBenchName(params, "Encryptor/Encrypt/Sk"), func(b *testing.B) { + + pt := heint.NewPlaintext(params, params.MaxLevel()) + + poly := tc.uSampler.ReadNew() + params.RingT().Reduce(poly, poly) + + if err := tc.encoder.Encode(poly.Coeffs[0], pt); err != nil { + b.Log(err) + b.Fail() + } + + ct := heint.NewCiphertext(params, 1, pt.Level()) + + enc := tc.encryptorSk + + b.ResetTimer() + + for i := 0; i < b.N; i++ { + if err := enc.Encrypt(pt, ct); err != nil { + b.Log(err) + b.Fail() + } + } + }) + + b.Run(GetBenchName(params, "Encryptor/Encrypt/Pk"), func(b *testing.B) { + + pt := heint.NewPlaintext(params, params.MaxLevel()) + + poly := tc.uSampler.ReadNew() + params.RingT().Reduce(poly, poly) + + if err := tc.encoder.Encode(poly.Coeffs[0], pt); err != nil { + b.Log(err) + b.Fail() + } + + ct := heint.NewCiphertext(params, 1, pt.Level()) + + enc := tc.encryptorPk + + b.ResetTimer() + + for i := 0; i < b.N; i++ { + if err := enc.Encrypt(pt, ct); err != nil { + b.Log(err) + b.Fail() + } + } + }) + + b.Run(GetBenchName(params, "Decryptor/Decrypt"), func(b *testing.B) { + + pt := heint.NewPlaintext(params, params.MaxLevel()) + + ct := rlwe.NewCiphertextRandom(tc.prng, params.Parameters, 1, params.MaxLevel()) + + *ct.MetaData = *pt.MetaData + + dec := tc.decryptor + + b.ResetTimer() + + for i := 0; i < b.N; i++ { + dec.Decrypt(ct, pt) + } + }) +} + func benchEvaluator(tc *testContext, b *testing.B) { params := tc.params From 2dc03dbc7a6b30f71408e43c2cef1087dfd3c5a0 Mon Sep 17 00:00:00 2001 From: Jean-Philippe Bossuat Date: Thu, 23 Nov 2023 11:36:40 +0100 Subject: [PATCH 6/6] [hebin]: added benchmarks --- he/hebin/blindrotation_benchmark_test.go | 72 ++++++++++++++++++++++++ he/hebin/blindrotation_test.go | 2 + he/hebin/evaluator.go | 40 +++++++------ 3 files changed, 96 insertions(+), 18 deletions(-) create mode 100644 he/hebin/blindrotation_benchmark_test.go diff --git a/he/hebin/blindrotation_benchmark_test.go b/he/hebin/blindrotation_benchmark_test.go new file mode 100644 index 000000000..a6019ffa3 --- /dev/null +++ b/he/hebin/blindrotation_benchmark_test.go @@ -0,0 +1,72 @@ +package hebin + +import ( + "testing" + + "github.com/tuneinsight/lattigo/v5/core/rlwe" + "github.com/tuneinsight/lattigo/v5/utils" + "github.com/tuneinsight/lattigo/v5/utils/sampling" + + "github.com/stretchr/testify/require" +) + +func BenchmarkHEBin(b *testing.B) { + + b.Run("BlindRotateCore/LogN=(9, 10)/LogQ=(13.6,26.99)/Gadget=2^7", func(b *testing.B) { + + // RLWE parameters of the BlindRotation + // N=1024, Q=0x7fff801 -> 131 bit secure + paramsBR, err := rlwe.NewParametersFromLiteral(rlwe.ParametersLiteral{ + LogN: 10, + Q: []uint64{0x7fff801}, + NTTFlag: NTTFlag, + }) + + require.NoError(b, err) + + // RLWE parameters of the samples + // N=512, Q=0x3001 -> 135 bit secure + paramsLWE, err := rlwe.NewParametersFromLiteral(rlwe.ParametersLiteral{ + LogN: 9, + Q: []uint64{0x3001}, + NTTFlag: NTTFlag, + }) + + require.NoError(b, err) + + evkParams := rlwe.EvaluationKeyParameters{BaseTwoDecomposition: utils.Pointy(7)} + + // RLWE secret for the samples + skLWE := rlwe.NewKeyGenerator(paramsLWE).GenSecretKeyNew() + + // Secret of the RGSW ciphertexts encrypting the bits of skLWE + skBR := rlwe.NewKeyGenerator(paramsBR).GenSecretKeyNew() + + // Collection of RGSW ciphertexts encrypting the bits of skLWE under skBR + BRK := GenEvaluationKeyNew(paramsBR, skBR, paramsLWE, skLWE, evkParams) + + // Random LWE mask mod 2N with odd coefficients + a := make([]uint64, paramsLWE.N()) + mask := uint64(2*paramsLWE.N() - 1) + for i := range a { + ai := sampling.RandUint64() & mask + if ai&1 == 0 && ai != 0 { + ai ^= 1 + } + a[i] = ai + } + + acc := rlwe.NewCiphertext(paramsBR, 1, paramsBR.MaxLevel()) + + // Evaluator for the Blind Rotation evaluation + eval := NewEvaluator(paramsBR, paramsLWE) + + b.ResetTimer() + + for i := 0; i < b.N; i++ { + if err := eval.BlindRotateCore(a, acc, BRK); err != nil { + panic(err) + } + } + }) +} diff --git a/he/hebin/blindrotation_test.go b/he/hebin/blindrotation_test.go index 3966523ea..50a65c96b 100644 --- a/he/hebin/blindrotation_test.go +++ b/he/hebin/blindrotation_test.go @@ -66,6 +66,8 @@ func testBlindRotation(t *testing.T) { NTTFlag: NTTFlag, }) + require.NoError(t, err) + evkParams := rlwe.EvaluationKeyParameters{BaseTwoDecomposition: utils.Pointy(7)} require.NoError(t, err) diff --git a/he/hebin/evaluator.go b/he/hebin/evaluator.go index 4987f03aa..c662d2e39 100644 --- a/he/hebin/evaluator.go +++ b/he/hebin/evaluator.go @@ -67,22 +67,14 @@ func (eval *Evaluator) EvaluateAndRepack(ct *rlwe.Ciphertext, testPolyWithSlotIn // Evaluate extracts on the fly LWE samples and evaluates the provided blind rotation on the LWE. // testPolyWithSlotIndex : a map with [slot_index] -> blind rotation // Returns a map[slot_index] -> BlindRotate(ct[slot_index]) -func (eval *Evaluator) Evaluate(ct *rlwe.Ciphertext, testPolyWithSlotIndex map[int]*ring.Poly, key BlindRotationEvaluationKeySet) (res map[int]*rlwe.Ciphertext, err error) { - - evk, err := key.GetEvaluationKeySet() - - if err != nil { - return nil, err - } - - eval.Evaluator = eval.Evaluator.WithKey(evk) +func (eval *Evaluator) Evaluate(ct *rlwe.Ciphertext, testPolyWithSlotIndex map[int]*ring.Poly, BRK BlindRotationEvaluationKeySet) (res map[int]*rlwe.Ciphertext, err error) { bRLWEMod2N := eval.poolMod2N[0] aRLWEMod2N := eval.poolMod2N[1] acc := eval.accumulator - brk, err := key.GetBlindRotationKey(0) + brk, err := BRK.GetBlindRotationKey(0) if err != nil { return nil, err @@ -140,7 +132,7 @@ func (eval *Evaluator) Evaluate(ct *rlwe.Ciphertext, testPolyWithSlotIndex map[i acc.Value[1].Zero() // Line 3 of Algorithm 7 https://eprint.iacr.org/2022/198 (Algorithm 3 of https://eprint.iacr.org/2022/198) - if err = eval.BlindRotateCore(a, acc, key); err != nil { + if err = eval.BlindRotateCore(a, acc, BRK); err != nil { return nil, fmt.Errorf("BlindRotateCore: %s", err) } @@ -159,7 +151,15 @@ func (eval *Evaluator) Evaluate(ct *rlwe.Ciphertext, testPolyWithSlotIndex map[i } // BlindRotateCore implements Algorithm 3 of https://eprint.iacr.org/2022/198 -func (eval *Evaluator) BlindRotateCore(a []uint64, acc *rlwe.Ciphertext, evk BlindRotationEvaluationKeySet) (err error) { +func (eval *Evaluator) BlindRotateCore(a []uint64, acc *rlwe.Ciphertext, BRK BlindRotationEvaluationKeySet) (err error) { + + evk, err := BRK.GetEvaluationKeySet() + + if err != nil { + return err + } + + eval.Evaluator = eval.Evaluator.WithKey(evk) // GaloisElement(k) = GaloisGen^{k} mod 2N GaloisElement := eval.paramsBR.GaloisElement @@ -173,13 +173,13 @@ func (eval *Evaluator) BlindRotateCore(a []uint64, acc *rlwe.Ciphertext, evk Bli var v int // Lines 3 to 9 (negative set of a[i] = -g^{k} mod 2N) for i := Nhalf - 1; i > 0; i-- { - if v, err = eval.evaluateFromDiscreteLogSets(GaloisElement, discreteLogSets, -i, v, acc, evk); err != nil { + if v, err = eval.evaluateFromDiscreteLogSets(GaloisElement, discreteLogSets, -i, v, acc, BRK); err != nil { return } } // Line 10 (0 in the negative set is 2N) - if _, err = eval.evaluateFromDiscreteLogSets(GaloisElement, discreteLogSets, eval.paramsBR.N()<<1, 0, acc, evk); err != nil { + if _, err = eval.evaluateFromDiscreteLogSets(GaloisElement, discreteLogSets, eval.paramsBR.N()<<1, 0, acc, BRK); err != nil { return } @@ -191,13 +191,13 @@ func (eval *Evaluator) BlindRotateCore(a []uint64, acc *rlwe.Ciphertext, evk Bli // Lines 13 - 19 (positive set of a[i] = g^{k} mod 2N) for i := Nhalf - 1; i > 0; i-- { - if v, err = eval.evaluateFromDiscreteLogSets(GaloisElement, discreteLogSets, i, v, acc, evk); err != nil { + if v, err = eval.evaluateFromDiscreteLogSets(GaloisElement, discreteLogSets, i, v, acc, BRK); err != nil { return } } // Lines 20 - 21 (0 in the positive set is 0) - if _, err = eval.evaluateFromDiscreteLogSets(GaloisElement, discreteLogSets, 0, 0, acc, evk); err != nil { + if _, err = eval.evaluateFromDiscreteLogSets(GaloisElement, discreteLogSets, 0, 0, acc, BRK); err != nil { return } @@ -205,7 +205,7 @@ func (eval *Evaluator) BlindRotateCore(a []uint64, acc *rlwe.Ciphertext, evk Bli } // evaluateFromDiscreteLogSets loops of Algorithm 3 of https://eprint.iacr.org/2022/198 -func (eval *Evaluator) evaluateFromDiscreteLogSets(GaloisElement func(k int) (galEl uint64), sets map[int][]int, k, v int, acc *rlwe.Ciphertext, evk BlindRotationEvaluationKeySet) (int, error) { +func (eval *Evaluator) evaluateFromDiscreteLogSets(GaloisElement func(k int) (galEl uint64), sets map[int][]int, k, v int, acc *rlwe.Ciphertext, BRK BlindRotationEvaluationKeySet) (int, error) { // Checks if k is in the discrete log sets if set, ok := sets[k]; ok { @@ -222,7 +222,7 @@ func (eval *Evaluator) evaluateFromDiscreteLogSets(GaloisElement func(k int) (ga for _, j := range set { - brk, err := evk.GetBlindRotationKey(j) + brk, err := BRK.GetBlindRotationKey(j) if err != nil { return v, err } @@ -276,6 +276,10 @@ func (eval *Evaluator) getDiscreteLogSets(a []uint64) (discreteLogSets map[int][ discreteLogSets = map[int][]int{} for i, ai := range a { + if ai&1 != 1 && ai != 0 { + panic("getDiscreteLogSets: a[i] is not odd and thus not an element of Z_{2N}^{*} -> a[i] = (+/- 1) * g^{k} does not exist.") + } + dlog := GaloisGenDiscreteLog[ai] if _, ok := discreteLogSets[dlog]; !ok {