Ответ 1
К счастью, rb_ary_memsize
является публичной функцией, поэтому с небольшим взломом вы можете это сделать:
#include <ruby.h>
#include <assert.h>
/* private macros from array.c */
#define ARY_OWNS_HEAP_P(a) (!FL_TEST((a), ELTS_SHARED|RARRAY_EMBED_FLAG))
#define ARY_SHARED_P(ary) \
(assert(!FL_TEST((ary), ELTS_SHARED) || !FL_TEST((ary), RARRAY_EMBED_FLAG)), \
FL_TEST((ary),ELTS_SHARED)!=0)
RUBY_FUNC_EXPORTED size_t
rb_ary_memsize(VALUE ary)
{
if (ARY_OWNS_HEAP_P(ary)) {
return RARRAY(ary)->as.heap.aux.capa * sizeof(VALUE);
}
/* -------8<------8<------- */
else if (ARY_SHARED_P(ary)){
/* if it is a shared array, calculate size using length of shared root */
return RARRAY_LEN(RARRAY(ary)->as.heap.aux.shared) * sizeof(VALUE);
}
/* ------->8------>8------- */
else {
return 0;
}
}
Скомпилируйте его в общий объект:
gcc $(ruby -rrbconfig \
-e'puts RbConfig::CONFIG.values_at("rubyhdrdir","rubyarchhdrdir").map{|d| " -I#{d}"}.join') \
-Wall -fpic -shared -o ary_memsize_hack.so ary_memsize_hack.c
И загрузите процесс, заменив оригинальную функцию:
LD_PRELOAD="$(pwd)/ary_memsize_hack.so" ruby -robjspace \
-e 'p ObjectSpace.memsize_of([0] * 1_000_000);
p ObjectSpace.memsize_of(Array.new([0] * 1_000_000))'
Он выдает желаемый результат:
8000040
8000040
UPDATE:
rb_ary_memsize
, которая отвечает за оценку размера массива, только делает это для массивов, которые владеют кучей (т.е. не разделены и не внедрены), и возвращает ноль в противном случае. В целом это имеет смысл, потому что, если вы планируете вычислять размер всех массивов в приложениях, в конечном итоге номера должны совпадать, в то время как с моим патчем содержимое общих массивов будет засчитываться несколько раз. Я предполагаю, что основная проблема заключается в том, как массив обтекания, построенный на рубиновой стороне: по существу ссылка на внутренний массив потеряна и недоступна по коду приложения и выглядит несчетной. Мой патч только демонстрирует, как добраться до корня общего массива, чтобы показать размер, но я не думаю, что это должно быть интегрировано в восходящий поток каким-либо образом. Аналогичная проблема будет связана со встроенными массивами, поскольку ruby также возвращает 0 как размер, который не показывает, что приложение ожидает увидеть:
require 'objspace'
puts ObjectSpace.dump([1])
#=> {"address":"0x000008033f9bd8", "type":"ARRAY", "class":"0x000008029f9a98", "length":1,
# "embedded":true, "memsize":40, "flags":{"wb_protected":true}}
puts ObjectSpace.dump([1, 2])
#=> {"address":"0x000008033f9b38", "type":"ARRAY", "class":"0x000008029f9a98", "length":2,
# "embedded":true, "memsize":40, "flags":{"wb_protected":true}}
puts ObjectSpace.dump([1, 2, 3])
#=> {"address":"0x000008033f9ac0", "type":"ARRAY", "class":"0x000008029f9a98", "length":3,
# "embedded":true, "memsize":40, "flags":{"wb_protected":true}}
puts ObjectSpace.dump([1, 2, 3, 4])
#=> {"address":"0x000008033f9a48", "type":"ARRAY", "class":"0x000008029f9a98", "length":4,
# "memsize":72, "flags":{"wb_protected":true}}