I wrote a small benchmark program to compare three methods of implementing atomic increment in Ada:
- Classic Ada protected object
- System.Atomic_Operations.Integer_Arithmetic (since Ada 2022)
- GCC built-in
Here is the code:
pragma Ada_2022;
with Ada.Calendar; use Ada.Calendar;
with Ada.Text_IO; use Ada.Text_IO;
with Interfaces.C; use Interfaces.C;
with System.Atomic_Operations.Integer_Arithmetic;
procedure Lock_Free_Benchmark is
function Add
( Ptr : access Integer;
Val : Integer;
Memorder : int := 0
) return Integer;
pragma Import (Intrinsic, Add, "__atomic_add_fetch_4");
protected Protected_Integer is
procedure Increment;
function Get return Integer;
private
Value : Integer := 0;
end Protected_Integer;
protected body Protected_Integer is
procedure Increment is
begin
Value := Value + 1;
end Increment;
function Get return Integer is
begin
return Value;
end Get;
end Protected_Integer;
type Atomic_Integer is new Integer with Atomic;
package Atomic_Integers is
new System.Atomic_Operations.Integer_Arithmetic (Atomic_Integer);
use Atomic_Integers;
Start : Time;
T1, T2, T3 : Duration;
X : aliased Atomic_Integer := 0;
Y, Z : aliased Integer := 0;
Times : constant := 100_000;
procedure Report (T : Duration; Text : String) is
begin
Put (Text);
Put (Duration'Image (T));
Put (Integer'Image (Integer ((T * 1000_000_000) / Times)));
Put ("ns ");
New_Line;
end Report;
begin
Start := Clock;
for I in 1..Times loop
Protected_Integer.Increment;
end loop;
T1 := Clock - Start;
Start := Clock;
for I in 1..Times loop
Atomic_Add (X, 1);
end loop;
T2 := Clock - Start;
Start := Clock;
for I in 1..Times loop
Z := Add (Y'Access, 1);
end loop;
T3 := Clock - Start;
Put_Line ("Method Total Time");
Report (T1, "protected object: ");
Report (T2, "atomic integer: ");
Report (T3, "gcc built-in: ");
end Lock_Free_Benchmark;
As expected the second two are almost same. Protected object is 2/3 times slower, which is quite good in my view.
Here are measures.
Debian:
Method Total Time
protected object: 0.000938000 9ns
atomic integer: 0.000536000 5ns
gcc built-in: 0.000382000 4ns
Windows:
Method Total Time
protected object: 0.001584700 16ns
atomic integer: 0.000534000 5ns
gcc built-in: 0.000384800 4ns
Interestingly, Linux is faster, maybe because it uses a simpler locks than Windows. Remember discussion on external protected action which block under Linux but does not under Windows? It seems that Windows is slower for that same reason.