Skip to content

Commit bb643f2

Browse files
committed
Merge pull request #130 from ruby-concurrency/refactor/native-atomics
CAtomicBoolean and CAtomicFixnum
2 parents ac140b1 + d3d322b commit bb643f2

19 files changed

+508
-106
lines changed

.yardopts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
--template-path ./yard-template
99

1010
./lib/**/*.rb
11+
./ext/concurrent_ruby_ext/**/*.c
1112
-
1213
README.md
1314
LICENSE.txt

examples/benchmark_atomic_boolean.rb

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
#!/usr/bin/env ruby
2+
3+
$:.push File.join(File.dirname(__FILE__), '../lib')
4+
5+
require 'concurrent'
6+
require 'benchmark'
7+
require 'rbconfig'
8+
9+
def atomic_test(clazz, opts = {})
10+
threads = opts.fetch(:threads, 5)
11+
tests = opts.fetch(:tests, 100)
12+
13+
atomic = clazz.new
14+
latch = Concurrent::CountDownLatch.new(threads)
15+
16+
print "Testing with #{clazz}...\n"
17+
stats = Benchmark.measure do
18+
threads.times do |i|
19+
Thread.new do
20+
tests.times{ atomic.value = true }
21+
latch.count_down
22+
end
23+
end
24+
latch.wait
25+
end
26+
print stats
27+
end
28+
29+
puts "Testing with #{RbConfig::CONFIG['ruby_install_name']} #{RUBY_VERSION}"
30+
31+
atomic_test(Concurrent::MutexAtomicBoolean, threads: 10, tests: 1_000_000)
32+
33+
if defined? Concurrent::CAtomicBoolean
34+
atomic_test(Concurrent::CAtomicBoolean, threads: 10, tests: 1_000_000)
35+
elsif RUBY_PLATFORM == 'java'
36+
atomic_test(Concurrent::JavaAtomicBoolean, threads: 10, tests: 1_000_000)
37+
end

examples/benchmark_atomic_fixnum.rb

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
#!/usr/bin/env ruby
2+
3+
$:.push File.join(File.dirname(__FILE__), '../lib')
4+
5+
require 'concurrent'
6+
require 'benchmark'
7+
require 'rbconfig'
8+
9+
def atomic_test(clazz, opts = {})
10+
threads = opts.fetch(:threads, 5)
11+
tests = opts.fetch(:tests, 100)
12+
13+
num = clazz.new
14+
latch = Concurrent::CountDownLatch.new(threads)
15+
16+
print "Testing with #{clazz}...\n"
17+
stats = Benchmark.measure do
18+
threads.times do |i|
19+
Thread.new do
20+
tests.times{ num.up }
21+
latch.count_down
22+
end
23+
end
24+
latch.wait
25+
end
26+
print stats
27+
end
28+
29+
puts "Testing with #{RbConfig::CONFIG['ruby_install_name']} #{RUBY_VERSION}"
30+
31+
atomic_test(Concurrent::MutexAtomicFixnum, threads: 10, tests: 1_000_000)
32+
33+
if defined? Concurrent::CAtomicFixnum
34+
atomic_test(Concurrent::CAtomicFixnum, threads: 10, tests: 1_000_000)
35+
elsif RUBY_PLATFORM == 'java'
36+
atomic_test(Concurrent::JavaAtomicFixnum, threads: 10, tests: 1_000_000)
37+
end
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
#include <ruby.h>
2+
3+
#include "atomic_boolean.h"
4+
#include "atomic_reference.h"
5+
#include "common.h"
6+
7+
void atomic_boolean_mark(void *value) {
8+
rb_gc_mark_maybe((VALUE) value);
9+
}
10+
11+
VALUE atomic_boolean_allocate(VALUE klass) {
12+
return rb_data_object_alloc(klass, (void *) Qfalse, atomic_boolean_mark, NULL);
13+
}
14+
15+
VALUE method_atomic_boolean_initialize(int argc, VALUE* argv, VALUE self) {
16+
VALUE value = Qfalse;
17+
rb_check_arity(argc, 0, 1);
18+
if (argc == 1) value = TRUTHY(argv[0]);
19+
DATA_PTR(self) = (void *) value;
20+
return(self);
21+
}
22+
23+
VALUE method_atomic_boolean_value(VALUE self) {
24+
return (VALUE) DATA_PTR(self);
25+
}
26+
27+
VALUE method_atomic_boolean_value_set(VALUE self, VALUE value) {
28+
VALUE new_value = TRUTHY(value);
29+
DATA_PTR(self) = (void *) new_value;
30+
return(new_value);
31+
}
32+
33+
VALUE method_atomic_boolean_true_question(VALUE self) {
34+
return(method_atomic_boolean_value(self));
35+
}
36+
37+
VALUE method_atomic_boolean_false_question(VALUE self) {
38+
VALUE current = method_atomic_boolean_value(self);
39+
return(current == Qfalse ? Qtrue : Qfalse);
40+
}
41+
42+
VALUE method_atomic_boolean_make_true(VALUE self) {
43+
return(ir_compare_and_set(self, Qfalse, Qtrue));
44+
}
45+
46+
VALUE method_atomic_boolean_make_false(VALUE self) {
47+
return(ir_compare_and_set(self, Qtrue, Qfalse));
48+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#ifndef __ATOMIC_BOOLEAN_H__
2+
#define __ATOMIC_BOOLEAN_H__
3+
4+
#define TRUTHY(value)(value == Qfalse || value == Qnil ? Qfalse : Qtrue)
5+
6+
void atomic_boolean_mark(void*);
7+
VALUE atomic_boolean_allocate(VALUE);
8+
VALUE method_atomic_boolean_initialize(int, VALUE*, VALUE);
9+
VALUE method_atomic_boolean_value(VALUE);
10+
VALUE method_atomic_boolean_value_set(VALUE, VALUE);
11+
VALUE method_atomic_boolean_true_question(VALUE);
12+
VALUE method_atomic_boolean_false_question(VALUE);
13+
VALUE method_atomic_boolean_make_true(VALUE);
14+
VALUE method_atomic_boolean_make_false(VALUE);
15+
16+
#endif
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
#include <ruby.h>
2+
3+
#include "atomic_fixnum.h"
4+
#include "atomic_reference.h"
5+
#include "common.h"
6+
7+
void atomic_fixnum_mark(void *value) {
8+
rb_gc_mark_maybe((VALUE) value);
9+
}
10+
11+
VALUE atomic_fixnum_allocate(VALUE klass) {
12+
return rb_data_object_alloc(klass, (void *) Qnil, atomic_fixnum_mark, NULL);
13+
}
14+
15+
VALUE method_atomic_fixnum_initialize(int argc, VALUE* argv, VALUE self) {
16+
VALUE value = LL2NUM(0);
17+
rb_check_arity(argc, 0, 1);
18+
if (argc == 1) {
19+
Check_Type(argv[0], T_FIXNUM);
20+
value = argv[0];
21+
}
22+
DATA_PTR(self) = (void *) value;
23+
return(self);
24+
}
25+
26+
VALUE method_atomic_fixnum_value(VALUE self) {
27+
return (VALUE) DATA_PTR(self);
28+
}
29+
30+
VALUE method_atomic_fixnum_value_set(VALUE self, VALUE value) {
31+
Check_Type(value, T_FIXNUM);
32+
DATA_PTR(self) = (void *) value;
33+
return(value);
34+
}
35+
36+
VALUE method_atomic_fixnum_increment(VALUE self) {
37+
long long value = NUM2LL((VALUE) DATA_PTR(self));
38+
return method_atomic_fixnum_value_set(self, LL2NUM(value + 1));
39+
}
40+
41+
VALUE method_atomic_fixnum_decrement(VALUE self) {
42+
long long value = NUM2LL((VALUE) DATA_PTR(self));
43+
return method_atomic_fixnum_value_set(self, LL2NUM(value - 1));
44+
}
45+
46+
VALUE method_atomic_fixnum_compare_and_set(VALUE self, VALUE rb_expect, VALUE rb_update) {
47+
Check_Type(rb_expect, T_FIXNUM);
48+
Check_Type(rb_update, T_FIXNUM);
49+
return ir_compare_and_set(self, rb_expect, rb_update);
50+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#ifndef __ATOMIC_FIXNUM_H__
2+
#define __ATOMIC_FIXNUM_H__
3+
4+
void atomic_fixnum_mark(void*);
5+
VALUE atomic_fixnum_allocate(VALUE);
6+
VALUE method_atomic_fixnum_initialize(int, VALUE*, VALUE);
7+
VALUE method_atomic_fixnum_value(VALUE);
8+
VALUE method_atomic_fixnum_value_set(VALUE, VALUE);
9+
VALUE method_atomic_fixnum_increment(VALUE);
10+
VALUE method_atomic_fixnum_decrement(VALUE);
11+
VALUE method_atomic_fixnum_compare_and_set(VALUE, VALUE, VALUE);
12+
13+
#endif
Lines changed: 44 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,78 +1,78 @@
11
#include <ruby.h>
2-
#if defined(__sun)
3-
#include <atomic.h>
4-
#endif
2+
/*#if defined(__sun)*/
3+
/*#include <atomic.h>*/
4+
/*#endif*/
55

6-
#ifdef HAVE_LIBKERN_OSATOMIC_H
7-
#include <libkern/OSAtomic.h>
8-
#endif
6+
/*#ifdef HAVE_LIBKERN_OSATOMIC_H*/
7+
/*#include <libkern/OSAtomic.h>*/
8+
/*#endif*/
99

1010
#include "atomic_reference.h"
1111

1212
void ir_mark(void *value) {
13-
rb_gc_mark_maybe((VALUE) value);
13+
rb_gc_mark_maybe((VALUE) value);
1414
}
1515

1616
VALUE ir_alloc(VALUE klass) {
17-
return rb_data_object_alloc(klass, (void *) Qnil, ir_mark, NULL);
17+
return rb_data_object_alloc(klass, (void *) Qnil, ir_mark, NULL);
1818
}
1919

2020
VALUE ir_initialize(int argc, VALUE* argv, VALUE self) {
21-
VALUE value = Qnil;
22-
if (rb_scan_args(argc, argv, "01", &value) == 1) {
23-
value = argv[0];
24-
}
25-
DATA_PTR(self) = (void *) value;
26-
return Qnil;
21+
VALUE value = Qnil;
22+
if (rb_scan_args(argc, argv, "01", &value) == 1) {
23+
value = argv[0];
24+
}
25+
DATA_PTR(self) = (void *) value;
26+
return Qnil;
2727
}
2828

2929
VALUE ir_get(VALUE self) {
30-
return (VALUE) DATA_PTR(self);
30+
return (VALUE) DATA_PTR(self);
3131
}
3232

3333
VALUE ir_set(VALUE self, VALUE new_value) {
34-
DATA_PTR(self) = (void *) new_value;
35-
return new_value;
34+
DATA_PTR(self) = (void *) new_value;
35+
return new_value;
3636
}
3737

3838
VALUE ir_get_and_set(VALUE self, VALUE new_value) {
39-
VALUE old_value;
40-
old_value = (VALUE) DATA_PTR(self);
41-
DATA_PTR(self) = (void *) new_value;
42-
return old_value;
39+
VALUE old_value;
40+
old_value = (VALUE) DATA_PTR(self);
41+
DATA_PTR(self) = (void *) new_value;
42+
return old_value;
4343
}
4444

4545
VALUE ir_compare_and_set(volatile VALUE self, VALUE expect_value, VALUE new_value) {
4646
#if __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ >= 1050
47-
if (OSAtomicCompareAndSwap64(expect_value, new_value, &DATA_PTR(self))) {
48-
return Qtrue;
49-
}
47+
if (OSAtomicCompareAndSwap64(expect_value, new_value, &DATA_PTR(self))) {
48+
return Qtrue;
49+
}
5050
#elif defined(__sun)
51-
/* Assuming VALUE is uintptr_t */
52-
/* Based on the definition of uintptr_t from /usr/include/sys/int_types.h */
51+
/* Assuming VALUE is uintptr_t */
52+
/* Based on the definition of uintptr_t from /usr/include/sys/int_types.h */
5353
#if defined(_LP64) || defined(_I32LPx)
54-
/* 64-bit: uintptr_t === unsigned long */
55-
if (atomic_cas_ulong((uintptr_t *) &DATA_PTR(self), expect_value, new_value)) {
56-
return Qtrue;
57-
}
54+
/* 64-bit: uintptr_t === unsigned long */
55+
if (atomic_cas_ulong((uintptr_t *) &DATA_PTR(self), expect_value, new_value)) {
56+
return Qtrue;
57+
}
5858
#else
59-
/* 32-bit: uintptr_t === unsigned int */
60-
if (atomic_cas_uint((uintptr_t *) &DATA_PTR(self), expect_value, new_value)) {
61-
return Qtrue;
62-
}
59+
/* 32-bit: uintptr_t === unsigned int */
60+
if (atomic_cas_uint((uintptr_t *) &DATA_PTR(self), expect_value, new_value)) {
61+
return Qtrue;
62+
}
6363
#endif
6464
#elif defined _MSC_VER && defined _M_AMD64
65-
if (InterlockedCompareExchange64((LONGLONG*)&DATA_PTR(self), new_value, expect_value)) {
66-
return Qtrue;
67-
}
65+
if (InterlockedCompareExchange64((LONGLONG*)&DATA_PTR(self), new_value, expect_value)) {
66+
return Qtrue;
67+
}
6868
#elif defined _MSC_VER && defined _M_IX86
69-
if (InterlockedCompareExchange((LONG*)&DATA_PTR(self), new_value, expect_value)) {
70-
return Qtrue;
71-
}
69+
if (InterlockedCompareExchange((LONG*)&DATA_PTR(self), new_value, expect_value)) {
70+
return Qtrue;
71+
}
7272
#else
73-
if (__sync_bool_compare_and_swap(&DATA_PTR(self), expect_value, new_value)) {
74-
return Qtrue;
75-
}
73+
if (__sync_bool_compare_and_swap(&DATA_PTR(self), expect_value, new_value)) {
74+
return Qtrue;
75+
}
7676
#endif
77-
return Qfalse;
77+
return Qfalse;
7878
}

ext/concurrent_ruby_ext/atomic_reference.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
11
#ifndef __ATOMIC_REFERENCE_H__
22
#define __ATOMIC_REFERENCE_H__
33

4+
#if defined(__sun)
5+
#include <atomic.h>
6+
#endif
7+
8+
#ifdef HAVE_LIBKERN_OSATOMIC_H
9+
#include <libkern/OSAtomic.h>
10+
#endif
11+
412
void ir_mark(void*);
513
VALUE ir_alloc(VALUE);
614
VALUE ir_initialize(int, VALUE*, VALUE);

ext/concurrent_ruby_ext/common.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#ifndef rb_check_arity
2+
3+
// https://github.com/ruby/ruby/blob/ruby_2_0_0/include/ruby/intern.h
4+
// rb_check_arity was added in Ruby 2.0
5+
6+
#define UNLIMITED_ARGUMENTS (-1)
7+
8+
#define rb_check_arity(argc, min, max) do { \
9+
if (((argc) < (min)) || ((argc) > (max) && (max) != UNLIMITED_ARGUMENTS)) \
10+
rb_error_arity(argc, min, max); \
11+
} while(0)
12+
13+
#endif

0 commit comments

Comments
 (0)