Rixmapなんていう画像処理ライブラリ作ってるわけなんですが、Rixmap::Colorクラスという色を表すクラスがメモリ食いなんで、メモ化 (参考:blog.pasberth.com: Ruby: newを再定義してインスタンスをメモ化)とかいうのを使って同じ色は同じオブジェクトを使いまわすようにしてるわけです。
で、ふと思ったのが、メモ化前と後だとどれだけメモリ量に差があるのかということでした。
試したのは
- メモ化なし (PureRuby)
- メモ化あり (PureRuby)
- メモ化なし (C拡張)
- メモ化あり (C拡張)
の四パターン。下2つはなんとなくData_Wrap_Structを使ってみたかったから試しただけなんですが。
メモリ計測用のスクリプトは次のようなもの
# -*- coding: utf-8 -*- colors = Array.new 256.times {|r| 256.times {|g| 256.times {|b| colors << Color.new(r, g, 0) } } } p colors.size $stdout.puts "press return to exit" $stdin.readline
意図的に青要素が0にしてあることで、重複が出るようにしてます。重複しないとメモ化の意味が無いので
まず結論
メモ化するとメモリ量1/10くらいになった (プログラムによる)。
メモリ使用量はこんな感じでした。
Pure Ruby | C拡張 | |
---|---|---|
メモ化なし | 863,624 KB | 732,068 KB |
メモ化あり | 81,376 KB | 77,604 KB |
それでも70MBは消費してるあたりヤバイ。
それはそうと相変わらずなんでメモ化っていうのかわかってない
使ったColorクラス定義
メモ化なしPureRuby
# -*- coding: utf-8 -*- # class Color attr_reader :red attr_reader :green attr_reader :blue attr_reader :alpha def initialize(r, g, b, a = 255) @red = r.to_i @green = g.to_i @blue = b.to_i @alpha = a.to_i end end
メモ化ありPureRuby
# -*- coding: utf-8 -*- # class Color attr_reader :red attr_reader :green attr_reader :blue attr_reader :alpha def self.new(*args) unless defined?(@memo) @memo = Hash.new do |memo, args| memo[args] = self.allocate.tap {|ins| ins.__send__(:initialize, *args) } end end return @memo[args] end def initialize(r, g, b, a = 255) @red = r.to_i @green = g.to_i @blue = b.to_i @alpha = a.to_i end end
メモ化なし(C拡張)
#include <cstdint> #include <ruby.h> typedef struct { uint8_t red; uint8_t green; uint8_t blue; uint8_t alpha; } ColorData; static VALUE Color_alloc(VALUE klass) { ColorData* ptr = ALLOC(ColorData); return Data_Wrap_Struct(klass, 0, -1, ptr); } static VALUE Color_initialize(int argc, VALUE* argv, VALUE self) { if (argc < 3) { rb_raise(rb_path2class("ArgumentError"), "Missing arguments"); } ColorData* ptr; Data_Get_Struct(self, ColorData, ptr); // 値を詰める ptr->red = NUM2INT(argv[0]); ptr->green = NUM2INT(argv[1]); ptr->blue = NUM2INT(argv[2]); ptr->alpha = 0; if (argc > 3) { ptr->alpha = NUM2INT(argv[3]); } return Qnil; } static VALUE Color_red_getter(VALUE self) { ColorData* ptr; Data_Get_Struct(self, ColorData, ptr); return INT2NUM(ptr->red); } static VALUE Color_green_getter(VALUE self) { ColorData* ptr; Data_Get_Struct(self, ColorData, ptr); return INT2NUM(ptr->green); } static VALUE Color_blue_getter(VALUE self) { ColorData* ptr; Data_Get_Struct(self, ColorData, ptr); return INT2NUM(ptr->blue); } static VALUE Color_alpha_getter(VALUE self) { ColorData* ptr; Data_Get_Struct(self, ColorData, ptr); return INT2NUM(ptr->alpha); } extern "C" void Init_color3() { VALUE cColor = rb_define_class("Color", rb_cObject); rb_define_alloc_func(cColor, Color_alloc); rb_define_private_method(cColor, "initialize", RUBY_METHOD_FUNC(Color_initialize), -1); rb_define_method(cColor, "red", RUBY_METHOD_FUNC(Color_red_getter), 0); rb_define_method(cColor, "green", RUBY_METHOD_FUNC(Color_green_getter), 0); rb_define_method(cColor, "blue", RUBY_METHOD_FUNC(Color_blue_getter), 0); rb_define_method(cColor, "alpha", RUBY_METHOD_FUNC(Color_alpha_getter), 0); }
メモ化あり(C拡張)
#include <tuple> #include <map> #include <cstdint> #include <ruby.h> typedef struct { uint8_t red; uint8_t green; uint8_t blue; uint8_t alpha; } ColorData; static std::map<std::tuple<uint8_t, uint8_t, uint8_t, uint8_t>, VALUE> colors; static VALUE cColor; static VALUE Color_new(int argc, VALUE* argv, VALUE klass) { if (argc < 3) { rb_raise(rb_path2class("ArgumentError"), "Missing arguments"); } uint8_t red = NUM2INT(argv[0]); uint8_t green = NUM2INT(argv[1]); uint8_t blue = NUM2INT(argv[2]); uint8_t alpha = 0; if (argc > 3) { alpha = NUM2INT(argv[3]); } std::tuple<uint8_t, uint8_t, uint8_t, uint8_t> args = std::make_tuple(red, green, blue, alpha); std::map<std::tuple<uint8_t, uint8_t, uint8_t, uint8_t>, VALUE>::iterator it = colors.find(args); VALUE ins; if (it == colors.end()) { ins = rb_obj_alloc(klass); rb_obj_call_init(ins, argc, argv); colors[args] = ins; } else { ins = it->second; } return ins; } static void Color_free(ColorData* ptr) { std::tuple<uint8_t, uint8_t, uint8_t, uint8_t> args = std::make_tuple(ptr->red, ptr->green, ptr->blue, ptr->alpha); std::map<std::tuple<uint8_t, uint8_t, uint8_t, uint8_t>, VALUE>::iterator it = colors.find(args); if (it != colors.end()) { colors.erase(it); } free(ptr); } static VALUE Color_alloc(VALUE klass) { ColorData* ptr = ALLOC(ColorData); return Data_Wrap_Struct(klass, 0, RUBY_DATA_FUNC(Color_free), ptr); } static VALUE Color_initialize(int argc, VALUE* argv, VALUE self) { if (argc < 3) { rb_raise(rb_path2class("ArgumentError"), "Missing arguments"); } ColorData* ptr; Data_Get_Struct(self, ColorData, ptr); // 値を詰める ptr->red = NUM2INT(argv[0]); ptr->green = NUM2INT(argv[1]); ptr->blue = NUM2INT(argv[2]); ptr->alpha = 0; if (argc > 3) { ptr->alpha = NUM2INT(argv[3]); } return Qnil; } static VALUE Color_red_getter(VALUE self) { ColorData* ptr; Data_Get_Struct(self, ColorData, ptr); return INT2NUM(ptr->red); } static VALUE Color_green_getter(VALUE self) { ColorData* ptr; Data_Get_Struct(self, ColorData, ptr); return INT2NUM(ptr->green); } static VALUE Color_blue_getter(VALUE self) { ColorData* ptr; Data_Get_Struct(self, ColorData, ptr); return INT2NUM(ptr->blue); } static VALUE Color_alpha_getter(VALUE self) { ColorData* ptr; Data_Get_Struct(self, ColorData, ptr); return INT2NUM(ptr->alpha); } extern "C" void Init_color4() { cColor = rb_define_class("Color", rb_cObject); rb_define_alloc_func(cColor, Color_alloc); rb_define_module_function(cColor, "new", RUBY_METHOD_FUNC(Color_new), -1); rb_define_private_method(cColor, "initialize", RUBY_METHOD_FUNC(Color_initialize), -1); rb_define_method(cColor, "red", RUBY_METHOD_FUNC(Color_red_getter), 0); rb_define_method(cColor, "green", RUBY_METHOD_FUNC(Color_green_getter), 0); rb_define_method(cColor, "blue", RUBY_METHOD_FUNC(Color_blue_getter), 0); rb_define_method(cColor, "alpha", RUBY_METHOD_FUNC(Color_alpha_getter), 0); }