chikuchikugonzalezの雑記帳

趣味とか日記とかメモとか(∩゚д゚)

メモ化を使った自作画像処理ライブラリでのメモリ削減試行

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);
}