Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Avoid relocation of strings by moving the content to heap memory
This is due to the fact that ruby-2.7+ relocates emdedded strings by GC.compact . See https://bugs.ruby-lang.org/issues/17023 for more description of the issue. Unfortunately there's no good way to pin string values to a fixed memory location in the ruby C API. The only way seems to be rb_gc_register_address(), but this function would keep a reference to the string object forever, so that it will never be freed. To work around this issue the string capacity is expanded, so that the string content is moved from embedded string memory to ordinary heap memory. The underlying string buffer of such strings is never relocated at GC.compact . There are some issues: 1. String expanding fails with frozen strings, but frozen strings are relocated as well. 2. All strings smaller than 24 characters are copied when passed to the C function. This increases call time by up to 70% although this copying isn't required in almost all cases. It's only needed when GC.compact is used and the string in question is accessed after GC.compact. 3. The string object is modified in a way that changes it's RSTRING_PTR(). That isn't visible in ruby, but could be noticed in conjunction with other C extensions.
- Loading branch information
Showing
5 changed files
with
82 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -32,3 +32,6 @@ string_null(void) | |
return NULL; | ||
} | ||
|
||
void store_string(char* str, char **ptr) { | ||
*ptr = str; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
# -*- encoding: utf-8 -*- | ||
# | ||
# This file is part of ruby-ffi. | ||
# For licensing, see LICENSE.SPECS | ||
# | ||
|
||
require File.expand_path(File.join(File.dirname(__FILE__), "spec_helper")) | ||
|
||
# These specs are not run as regular rspec file, but as a separate process from string_spec.rb. | ||
# That's due to GC.verify_compaction_references is very memory consuming since it doubles the needed memory with each call. | ||
# So it's intentionally that the file name miss the "_spec" prefix. | ||
|
||
if GC.respond_to?(:compact) | ||
describe "GC.compact" do | ||
module GcCompactTestLib | ||
extend FFI::Library | ||
ffi_lib TestLibrary::PATH | ||
attach_function :store_string, [ :string, :pointer ], :void | ||
attach_function :store_pointer, :store_string, [ :pointer, :pointer ], :void | ||
end | ||
|
||
A = [] | ||
F = [] | ||
[0, 1, 23, 24].each_with_index do |len, idx| | ||
A[idx] = "a" * len | ||
F[idx] = ("a" * len).freeze | ||
|
||
it "preserves a #{len} character string" do | ||
mem = FFI::MemoryPointer.new :pointer, 3 | ||
GcCompactTestLib.store_string(A[idx], mem) | ||
GcCompactTestLib.store_pointer(A[idx], mem + 1*FFI::TYPE_POINTER.size) | ||
GcCompactTestLib.store_string(F[idx], mem + 2*FFI::TYPE_POINTER.size) | ||
|
||
GC.verify_compaction_references(toward: :empty, double_heap: true) | ||
|
||
expect( mem.get_pointer(0*FFI::TYPE_POINTER.size).read_string ).to eq("a" * len) | ||
expect( mem.get_pointer(1*FFI::TYPE_POINTER.size).read_string ).to eq("a" * len) | ||
expect( mem.get_pointer(2*FFI::TYPE_POINTER.size).read_string ).to eq("a" * len) | ||
expect( A[idx] ).to eq("a" * len) | ||
expect( F[idx] ).to eq("a" * len) | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters