diff --git a/Cargo.toml b/Cargo.toml
index 6e3c6597652955e21c40416340addf866640f292..ec3431564628300cb3bfcd2f51174dabbd4972e7 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -65,6 +65,16 @@ harness = false
 name = "commit"
 harness = false
 
+[[bench]]
+name = "field_operations"
+path = "benches/operations/field.rs"
+harness = false
+
+[[bench]]
+name = "curve_group_operations"
+path = "benches/operations/curve_group.rs"
+harness = false
+
 [[example]]
 name = "bench_commit"
 path = "examples/benches/commit.rs"
diff --git a/benches/README.md b/benches/README.md
index 5feeb4cfe8d74614a80c9758eaddfbffcad4941a..6f72baa6bf8fd1dbae9650480564282020f72314 100644
--- a/benches/README.md
+++ b/benches/README.md
@@ -14,11 +14,34 @@ python scripts/plot/benches.py results.ndjson --bench linalg
 python scripts/plot/benches.py results.ndjson --bench setup
 ```
 
+## atomic operations
+```nushell
+cargo criterion --output-format verbose --message-format json --bench field_operations out> field.ndjson
+cargo criterion --output-format verbose --message-format json --bench curve_group_operations out> curve.ndjson
+```
+```nushell
+def read-atomic-ops []: list -> record {
+    where reason == "benchmark-complete"
+        | select id mean.estimate
+        | rename --column { mean_estimate: "mean" }
+        | sort-by id
+        | update id { parse "{op} on {curve}" }
+        | flatten --all
+        | group-by op --to-table
+        | reject items.op
+        | update items { transpose -r | into record }
+        | transpose -r
+        | into record
+}
+python scripts/plot/multi_bar.py (open field.ndjson | read-atomic-ops | to json) --title "field operations" -l "time (in ns)"
+python scripts/plot/multi_bar.py (open curve.ndjson | read-atomic-ops | to json) --title "curve group operations" -l "time (in ns)"
+```
+
 ## oneshot benchmarks
 these are benchmarks that run a single measurement, implemented as _examples_ in
 `examples/benches/`.
 
-## commit
+### commit
 ```nushell
 let res = cargo run --example bench_commit
     | lines
diff --git a/benches/operations/curve_group.rs b/benches/operations/curve_group.rs
new file mode 100644
index 0000000000000000000000000000000000000000..7a4047f9eca71921670ab5dbc725fda2c51f0110
--- /dev/null
+++ b/benches/operations/curve_group.rs
@@ -0,0 +1,112 @@
+// see `benches/README.md`
+use std::time::Duration;
+
+use ark_ec::CurveGroup;
+use ark_ff::PrimeField;
+
+use criterion::{criterion_group, criterion_main, Criterion};
+
+fn random_sampling_template<G: CurveGroup>(c: &mut Criterion, curve: &str) {
+    let mut rng = ark_std::test_rng();
+
+    c.bench_function(&format!("random sampling on {}", curve), |b| {
+        b.iter(|| {
+            let _ = G::rand(&mut rng);
+        });
+    });
+}
+
+fn group_template<F: PrimeField, G: CurveGroup<ScalarField = F>>(c: &mut Criterion, curve: &str) {
+    let mut rng = ark_std::test_rng();
+
+    c.bench_function(&format!("addition on {}", curve), |b| {
+        b.iter(|| {
+            let g1 = G::rand(&mut rng);
+            let g2 = G::rand(&mut rng);
+            g1 + g2
+        });
+    });
+
+    c.bench_function(&format!("substraction on {}", curve), |b| {
+        b.iter(|| {
+            let g1 = G::rand(&mut rng);
+            let g2 = G::rand(&mut rng);
+            g1 - g2
+        });
+    });
+
+    c.bench_function(&format!("double on {}", curve), |b| {
+        b.iter(|| {
+            let g1 = G::rand(&mut rng);
+            g1.double()
+        });
+    });
+
+    c.bench_function(&format!("scalar multiplication on {}", curve), |b| {
+        b.iter(|| {
+            let g1 = G::rand(&mut rng);
+            let f1 = F::rand(&mut rng);
+            g1.mul(f1)
+        });
+    });
+}
+
+fn curve_group_template<F: PrimeField, G: CurveGroup<ScalarField = F>>(
+    c: &mut Criterion,
+    curve: &str,
+) {
+    let mut rng = ark_std::test_rng();
+
+    c.bench_function(&format!("into affine on {}", curve), |b| {
+        b.iter(|| {
+            let g1 = G::rand(&mut rng);
+            g1.into_affine()
+        });
+    });
+
+    c.bench_function(&format!("from affine on {}", curve), |b| {
+        b.iter(|| {
+            let g1_affine = G::rand(&mut rng).into_affine();
+            let _: G = g1_affine.into();
+        });
+    });
+
+    c.bench_function(&format!("affine addition on {}", curve), |b| {
+        b.iter(|| {
+            let g1_affine = G::rand(&mut rng).into_affine();
+            let g2_affine = G::rand(&mut rng).into_affine();
+            g1_affine + g2_affine
+        });
+    });
+
+    c.bench_function(&format!("affine scalar multiplication on {}", curve), |b| {
+        b.iter(|| {
+            let g1_affine = G::rand(&mut rng).into_affine();
+            let f1 = F::rand(&mut rng);
+            g1_affine * f1
+        });
+    });
+}
+
+macro_rules! bench {
+    ($c:ident, $b:ident, G1=$g:ident, name=$n:expr) => {
+        random_sampling_template::<$c::$g>($b, $n);
+        group_template::<$c::Fr, $c::$g>($b, $n);
+        curve_group_template::<$c::Fr, $c::$g>($b, $n);
+    };
+}
+
+fn bench(c: &mut Criterion) {
+    bench!(ark_bls12_381, c, G1 = G1Projective, name = "BLS12-381");
+    bench!(ark_bn254, c, G1 = G1Projective, name = "BN-254");
+    bench!(ark_pallas, c, G1 = Projective, name = "PALLAS");
+}
+
+criterion_group!(
+    name = benches;
+    config = Criterion::default()
+        .warm_up_time(Duration::from_secs_f32(0.5))
+        .sample_size(10);
+    targets = bench
+);
+criterion_main!(benches);
diff --git a/benches/operations/field.rs b/benches/operations/field.rs
new file mode 100644
index 0000000000000000000000000000000000000000..f5d85bbd1c7b9a455eb4fd1517168f4c12d80e58
--- /dev/null
+++ b/benches/operations/field.rs
@@ -0,0 +1,132 @@
+// see `benches/README.md`
+use std::time::Duration;
+
+use ark_ff::PrimeField;
+
+use criterion::{criterion_group, criterion_main, Criterion};
+
+fn random_sampling_template<F: PrimeField>(c: &mut Criterion, curve: &str) {
+    let mut rng = ark_std::test_rng();
+
+    c.bench_function(&format!("random sampling on {}", curve), |b| {
+        b.iter(|| {
+            let _ = F::rand(&mut rng);
+        });
+    });
+}
+
+fn additive_group_template<F: PrimeField>(c: &mut Criterion, curve: &str) {
+    let mut rng = ark_std::test_rng();
+
+    c.bench_function(&format!("addition on {}", curve), |b| {
+        b.iter(|| {
+            let f1 = F::rand(&mut rng);
+            let f2 = F::rand(&mut rng);
+            f1 + f2
+        });
+    });
+
+    c.bench_function(&format!("substraction on {}", curve), |b| {
+        b.iter(|| {
+            let f1 = F::rand(&mut rng);
+            let f2 = F::rand(&mut rng);
+            f1 - f2
+        });
+    });
+
+    c.bench_function(&format!("double on {}", curve), |b| {
+        b.iter(|| {
+            let f1 = F::rand(&mut rng);
+            f1.double()
+        });
+    });
+
+    c.bench_function(&format!("multiplication on {}", curve), |b| {
+        b.iter(|| {
+            let f1 = F::rand(&mut rng);
+            let f2 = F::rand(&mut rng);
+            f1 * f2
+        });
+    });
+}
+
+fn field_template<F: PrimeField>(c: &mut Criterion, curve: &str) {
+    let mut rng = ark_std::test_rng();
+
+    c.bench_function(&format!("square on {}", curve), |b| {
+        b.iter(|| {
+            let f1 = F::rand(&mut rng);
+            f1.square()
+        });
+    });
+
+    c.bench_function(&format!("inverse on {}", curve), |b| {
+        b.iter(|| {
+            let f1 = F::rand(&mut rng);
+            f1.inverse()
+        });
+    });
+
+    c.bench_function(&format!("legendre on {}", curve), |b| {
+        b.iter(|| {
+            let f1 = F::rand(&mut rng);
+            f1.legendre()
+        });
+    });
+
+    c.bench_function(&format!("sqrt on {}", curve), |b| {
+        b.iter(|| {
+            let f1 = F::rand(&mut rng);
+            if f1.legendre().is_qr() {
+                let _ = f1.sqrt();
+            }
+        });
+    });
+}
+
+fn prime_field_template<F: PrimeField>(c: &mut Criterion, curve: &str) {
+    let mut rng = ark_std::test_rng();
+
+    c.bench_function(&format!("exponentiation on {}", curve), |b| {
+        b.iter(|| {
+            let f1 = F::rand(&mut rng);
+            f1.pow(F::MODULUS)
+        });
+    });
+}
+
+fn conversions_template<F: PrimeField>(c: &mut Criterion, curve: &str) {
+    let mut rng = ark_std::test_rng();
+
+    c.bench_function(&format!("bigint conversion on {}", curve), |b| {
+        b.iter(|| {
+            let f1 = F::rand(&mut rng);
+            f1.into_bigint()
+        });
+    });
+}
+
+macro_rules! bench {
+    ($c:ident, $b:ident, F=$f:ident, name=$n:expr) => {
+        random_sampling_template::<$c::$f>($b, $n);
+        additive_group_template::<$c::$f>($b, $n);
+        field_template::<$c::$f>($b, $n);
+        prime_field_template::<$c::$f>($b, $n);
+        conversions_template::<$c::$f>($b, $n);
+    };
+}
+
+fn bench(c: &mut Criterion) {
+    bench!(ark_bls12_381, c, F = Fr, name = "BLS12-381");
+    bench!(ark_bn254, c, F = Fr, name = "BN-254");
+    bench!(ark_pallas, c, F = Fr, name = "PALLAS");
+}
+
+criterion_group!(
+    name = benches;
+    config = Criterion::default()
+        .warm_up_time(Duration::from_secs_f32(0.5))
+        .sample_size(10);
+    targets = bench
+);
+criterion_main!(benches);
diff --git a/scripts/plot/multi_bar.py b/scripts/plot/multi_bar.py
new file mode 100644
index 0000000000000000000000000000000000000000..2cbee35add2192151b86ec840b16907d5bc18eb7
--- /dev/null
+++ b/scripts/plot/multi_bar.py
@@ -0,0 +1,125 @@
+# see `benches/README.md`
+from typing import Dict, List
+
+import matplotlib.pyplot as plt
+import numpy as np
+import json
+import sys
+import argparse
+
+
+# convert raw data into groups and measurements
+#
+# # Example
+# input:
+# ```json
+# {
+#     "age": {
+#         "alice": 31,
+#         "bob": 28,
+#         "charlie": 44
+#     },
+#     "height": {
+#         "alice": 1.50,
+#         "bob": 1.82,
+#         "charlie": 1.65
+#     },
+#     "weight": {
+#         "alice": 65.3,
+#         "bob": 98.1,
+#         "charlie": 68.7
+#     }
+# }
+# ```
+#
+# output:
+# ```
+# groups = ["age", "height", "weight"]
+# measurements = {
+#      "alice": [1.50, 31, 65.3],
+#      "bob": [1.82, 28, 98.1],
+#      "charlie": [1.65, 44, 68.7]
+# }
+# ```
+def extract(data: Dict[str, Dict[str, float]]) -> (List[str], Dict[str, List[float]]):
+    groups = list(data.keys())
+
+    measurements = {}
+    for x in data[groups[0]].keys():
+        measurements[x] = [data[g][x] for g in groups]
+
+    return (groups, measurements)
+
+
+# plot multi bars
+#
+# # Example
+# input can be the output of [`extract`]
+def plot_multi_bar(
+    groups: List[str],
+    measurements: Dict[str, List[float]],
+    title: str,
+    y_label: str,
+    labels_locations: List[float] = None,
+    width: float = 0.25,
+    nb_legend_cols: int = 3,
+    legend_loc: str = "upper left",
+    plot_layout: str = "constrained",
+    save: str = None,
+):
+    if labels_locations is None:
+        labels_locations = np.arange(len(groups))
+
+    fig, ax = plt.subplots(layout=plot_layout)
+
+    multiplier = 0
+    for attribute, measurement in measurements.items():
+        offset = width * multiplier
+        rects = ax.bar(labels_locations + offset, measurement, width, label=attribute)
+        ax.bar_label(rects, padding=3)
+        multiplier += 1
+
+    ax.set_ylabel(y_label)
+    ax.set_title(title)
+    ax.set_xticks(labels_locations + width, groups)
+    ax.legend(loc=legend_loc, ncols=nb_legend_cols)
+
+    if save is not None:
+        fig.set_size_inches((16, 9), forward=False)
+        fig.savefig(save, dpi=500)
+
+        print(f"plot saved as `{save}`")
+    else:
+        plt.show()
+
+
+if __name__ == "__main__":
+    parser = argparse.ArgumentParser()
+    parser.add_argument("data", type=str, help="""
+        the actual data to show in a multibar plot, here is an example:
+        {
+            "age": {
+                "alice": 31,
+                "bob": 28,
+                "charlie": 44
+            },
+            "height": {
+                "alice": 1.50,
+                "bob": 1.82,
+                "charlie": 1.65
+            },
+            "weight": {
+                "alice": 65.3,
+                "bob": 98.1,
+                "charlie": 68.7
+            }
+        }
+                        """)
+    parser.add_argument("--title", "-t", type=str, help="the title of the multibar plot")
+    parser.add_argument("--label", "-l", type=str, help="the measurement label of the multibar plot")
+    parser.add_argument("--save", "-s", type=str, help="a path to save the figure to")
+    args = parser.parse_args()
+
+    groups, measurements = extract(json.loads(args.data))
+
+    plot_multi_bar(groups, measurements, args.title, args.label, save=args.save)