Skip to content

Instantly share code, notes, and snippets.

@paddor
Last active November 29, 2015 22:15
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save paddor/760caa83c6ebd2eb20ab to your computer and use it in GitHub Desktop.
Save paddor/760caa83c6ebd2eb20ab to your computer and use it in GitHub Desktop.
#! /usr/bin/env ruby
require 'benchmark/ips'
require 'ffi'
module CZMQ
module FFI
module LibC
extend ::FFI::Library
ffi_lib ::FFI::Platform::LIBC
attach_function :free, [ :pointer ], :void, blocking: true
end
extend ::FFI::Library
def self.available?
@available
end
begin
lib_name = 'libczmq'
lib_paths = ['/usr/local/lib', '/opt/local/lib', '/usr/lib64']
.map { |path| "#{path}/#{lib_name}.#{::FFI::Platform::LIBSUFFIX}" }
ffi_lib lib_paths + [lib_name]
@available = true
rescue LoadError
warn ""
warn "WARNING: ::CZMQ::FFI is not available without libczmq."
warn ""
@available = false
end
if available?
opts = {
blocking: true # only necessary on MRI to deal with the GIL.
}
enum :zarmour_mode, [
:mode_base64_std, 0,
:mode_base64_url, 1,
:mode_base32_std, 2,
:mode_base32_hex, 3,
:mode_base16, 4,
:mode_z85, 5,
]
attach_function :zarmour_new, [], :pointer, **opts
attach_function :zarmour_destroy, [:pointer], :void, **opts
attach_function :zarmour_encode, [:pointer, :pointer, :size_t], :pointer, **opts
attach_function :zarmour_set_mode, [:pointer, :zarmour_mode], :void, **opts
end
# zarmour - armoured text encoding and decoding
class Zarmour
class DestroyedError < RuntimeError; end
# Boilerplate for self pointer, initializer, and finalizer
class << self
alias :__new :new
end
def initialize ptr, finalize=true
@ptr = ptr
if @ptr.null?
@ptr = nil # Remove null pointers so we don't have to test for them.
elsif finalize
@finalizer = self.class.create_finalizer_for @ptr
ObjectSpace.define_finalizer self, @finalizer
end
end
def self.create_finalizer_for ptr
Proc.new do
ptr_ptr = ::FFI::MemoryPointer.new :pointer
ptr_ptr.write_pointer ptr
::CZMQ::FFI.zarmour_destroy ptr_ptr
end
end
def null?
!@ptr or @ptr.null?
end
# Return internal pointer
def __ptr
raise DestroyedError unless @ptr
@ptr
end
# So external Libraries can just pass the Object to a FFI function which expects a :pointer
alias_method :to_ptr, :__ptr
# Nullify internal pointer and return pointer pointer
def __ptr_give_ref
raise DestroyedError unless @ptr
ptr_ptr = ::FFI::MemoryPointer.new :pointer
ptr_ptr.write_pointer @ptr
ObjectSpace.undefine_finalizer self if @finalizer
@finalizer = nil
@ptr = nil
ptr_ptr
end
# Create a new zarmour.
def self.new()
ptr = ::CZMQ::FFI.zarmour_new()
__new ptr
end
# Encode a stream of bytes into an armoured string.
def encode(data, data_size)
raise DestroyedError unless @ptr
self_p = @ptr
data_size = Integer(data_size)
result = ::CZMQ::FFI.zarmour_encode(self_p, data, data_size)
result = ::FFI::AutoPointer.new(result, LibC.method(:free))
result
end
def encode_immediate_free(data, data_size)
raise DestroyedError unless @ptr
self_p = @ptr
data_size = Integer(data_size)
cstring = ::CZMQ::FFI.zarmour_encode(self_p, data, data_size)
rubystring = cstring.read_string
LibC.free cstring
rubystring
end
# Set the mode property.
def set_mode(mode)
raise DestroyedError unless @ptr
self_p = @ptr
result = ::CZMQ::FFI.zarmour_set_mode(self_p, mode)
result
end
end
end
end
SHORT_STRING = FFI::MemoryPointer.from_string "foobar baz foo bar b"
SHORT_STRING_LEN = SHORT_STRING.size - 1 # without the null byte
LONG_STRING = FFI::MemoryPointer.from_string(("foobar foobar foobar" * 100))
LONG_STRING_LEN = LONG_STRING.size - 1 # without the null byte
ZARMOUR = CZMQ::FFI::Zarmour.new
ZARMOUR.set_mode(:mode_z85)
puts "SHORT STRING (#{SHORT_STRING_LEN} bytes)"
puts "=" * 78
Benchmark.ips do |x|
# Configure the number of seconds used during
# the warmup phase (default 2) and calculation phase (default 5)
x.config(:time => 5, :warmup => 2)
x.report("finalizer free()") do
ZARMOUR.encode(SHORT_STRING, SHORT_STRING_LEN)
end
x.report("immediate free()") do
ZARMOUR.encode_immediate_free(SHORT_STRING, SHORT_STRING_LEN)
end
x.compare!
end
puts
puts "LONG STRING (#{LONG_STRING_LEN} bytes)"
puts "=" * 78
Benchmark.ips do |x|
x.report("finalizer free()") do
ZARMOUR.encode(LONG_STRING, LONG_STRING_LEN)
end
x.report("immediate free()") do
ZARMOUR.encode_immediate_free(LONG_STRING, LONG_STRING_LEN)
end
x.compare!
end
$ ./benchmark_fresh_cstring.rb
SHORT STRING (20 bytes)
==============================================================================
Calculating -------------------------------------
finalizer free() 12.508k i/100ms
immediate free() 30.465k i/100ms
-------------------------------------------------
finalizer free() 173.257k (±48.7%) i/s - 612.892k
immediate free() 370.366k (±10.0%) i/s - 1.858M
Comparison:
immediate free(): 370366.5 i/s
finalizer free(): 173256.8 i/s - 2.14x slower
LONG STRING (2000 bytes)
==============================================================================
Calculating -------------------------------------
finalizer free() 4.500k i/100ms
immediate free() 5.332k i/100ms
-------------------------------------------------
finalizer free() 55.104k (±22.5%) i/s - 252.000k
immediate free() 62.283k (± 8.6%) i/s - 309.256k
Comparison:
immediate free(): 62283.3 i/s
finalizer free(): 55103.5 i/s - 1.13x slower
./benchmark_fresh_cstring.rb 26.23s user 2.25s system 99% cpu 28.584 total
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment