ZenHacks 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/History.txt +4 -0
- data/Manifest.txt +29 -0
- data/README.txt +84 -0
- data/bin/macgraph +18 -0
- data/bin/parse_tree_graph +161 -0
- data/bin/test_stats +42 -0
- data/fixloops-demo.sh +3 -0
- data/lib/OrderedHash.rb +37 -0
- data/lib/class-path.rb +20 -0
- data/lib/discover.rb +15 -0
- data/lib/fixloops.rb +79 -0
- data/lib/graph.rb +66 -0
- data/lib/muffdaddy.rb +84 -0
- data/lib/r2c_hacks.rb +36 -0
- data/lib/ruby2ruby.rb +306 -0
- data/lib/timezones.rb +11 -0
- data/lib/zendebug.rb +1037 -0
- data/lib/zenoptimize.rb +149 -0
- data/lib/zenprofile.rb +170 -0
- data/misc/factorial.rb +26 -0
- data/misc/find_c_methods +49 -0
- data/misc/fixloops-bad.rb +62 -0
- data/r2c_hacks-demo.rb +23 -0
- data/test/TestOrderedHash.rb +43 -0
- data/test/r2ctestcase.rb +1076 -0
- data/test/test_parse_tree_graph.rb +47 -0
- data/zendebug-demo.sh +86 -0
- data/zenoptimize-demo.sh +22 -0
- data/zenprofile-demo.sh +29 -0
- metadata +69 -0
data/lib/zenoptimize.rb
ADDED
@@ -0,0 +1,149 @@
|
|
1
|
+
require 'inline'
|
2
|
+
require 'singleton'
|
3
|
+
require 'ruby_to_c'
|
4
|
+
require 'pp'
|
5
|
+
|
6
|
+
module Inline
|
7
|
+
class Ruby < Inline::C
|
8
|
+
def optimize(meth)
|
9
|
+
src = RubyToC.translate(@mod, meth)
|
10
|
+
if $DEBUG then
|
11
|
+
STDERR.puts
|
12
|
+
STDERR.puts src
|
13
|
+
STDERR.puts
|
14
|
+
end
|
15
|
+
@mod.class_eval "alias :#{meth}_slow :#{meth}"
|
16
|
+
@mod.class_eval "remove_method :#{meth}"
|
17
|
+
c src
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class ZenOptimizer
|
23
|
+
|
24
|
+
include Singleton
|
25
|
+
|
26
|
+
@@threshold = 500
|
27
|
+
@@sacred = {
|
28
|
+
Sexp => true,
|
29
|
+
}
|
30
|
+
@@skip = Hash.new(false)
|
31
|
+
@@data = Hash.new(0)
|
32
|
+
|
33
|
+
def self.start_optimizing
|
34
|
+
self.instance.add_event_hook
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.stop_optimizing
|
38
|
+
self.instance.remove_event_hook
|
39
|
+
if $DEBUG then
|
40
|
+
STDERR.puts @@skip.inspect
|
41
|
+
STDERR.puts @@data.sort_by{|x,y| y}.reverse[0..4].inspect
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.optimize(signature)
|
46
|
+
klass, meth = *signature
|
47
|
+
|
48
|
+
unless @@sacred.include? klass then
|
49
|
+
STDERR.puts "*** Optimizer threshold tripped!! Optimizing #{klass}.#{meth}"
|
50
|
+
|
51
|
+
begin
|
52
|
+
klass.module_eval "inline(:Ruby) { |b| b.optimize(#{meth.inspect}) }"
|
53
|
+
rescue Exception => e
|
54
|
+
STDERR.puts "Failed to optimize #{klass}.#{meth}"
|
55
|
+
STDERR.puts "Exception = #{e.class}, message = #{e.message}"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
@@skip[signature] = true
|
60
|
+
end
|
61
|
+
|
62
|
+
############################################################
|
63
|
+
# Inlined Methods:
|
64
|
+
|
65
|
+
inline(:C) do |builder|
|
66
|
+
|
67
|
+
builder.add_type_converter("rb_event_t", '', '')
|
68
|
+
builder.add_type_converter("ID", '', '')
|
69
|
+
|
70
|
+
builder.include '"ruby.h"'
|
71
|
+
builder.include '"node.h"'
|
72
|
+
|
73
|
+
builder.prefix "static VALUE optimizer_klass = Qnil;
|
74
|
+
static VALUE data = Qnil;
|
75
|
+
static VALUE skip = Qnil;
|
76
|
+
static unsigned long threshold = 0;"
|
77
|
+
|
78
|
+
builder.c_raw <<-'EOF'
|
79
|
+
static void
|
80
|
+
prof_event_hook(rb_event_t event, NODE *node,
|
81
|
+
VALUE self, ID mid, VALUE klass) {
|
82
|
+
|
83
|
+
if (NIL_P(optimizer_klass))
|
84
|
+
optimizer_klass = rb_path2class("ZenOptimizer");
|
85
|
+
if (NIL_P(data))
|
86
|
+
data = rb_cv_get(optimizer_klass, "@@data");
|
87
|
+
if (NIL_P(skip))
|
88
|
+
skip = rb_cv_get(optimizer_klass, "@@skip");
|
89
|
+
if (threshold == 0)
|
90
|
+
threshold = NUM2ULONG(rb_cv_get(optimizer_klass, "@@threshold"));
|
91
|
+
|
92
|
+
switch (event) {
|
93
|
+
case RUBY_EVENT_CALL:
|
94
|
+
{
|
95
|
+
VALUE signature;
|
96
|
+
|
97
|
+
#if 0
|
98
|
+
VALUE mod_name = rb_mod_name(klass);
|
99
|
+
if (NIL_P(mod_name))
|
100
|
+
signature = rb_str_new2("Unknown");
|
101
|
+
else
|
102
|
+
signature = mod_name;
|
103
|
+
|
104
|
+
rb_str_cat(signature, ".", 1); // TODO: # or .
|
105
|
+
|
106
|
+
char * meth = rb_id2name(mid);
|
107
|
+
if (meth) {
|
108
|
+
size_t len = strlen(meth);
|
109
|
+
rb_str_cat(signature, meth, len);
|
110
|
+
} else {
|
111
|
+
rb_str_cat(signature, "unknown", 7);
|
112
|
+
}
|
113
|
+
#else
|
114
|
+
signature = rb_ary_new2(2);
|
115
|
+
rb_ary_store(signature, 0, klass);
|
116
|
+
rb_ary_store(signature, 1, ID2SYM(mid));
|
117
|
+
#endif
|
118
|
+
|
119
|
+
unsigned long count = NUM2ULONG(rb_hash_aref(data, signature)) + 1;
|
120
|
+
|
121
|
+
if (count > threshold) {
|
122
|
+
if (! RTEST(rb_hash_aref(skip, signature))) {
|
123
|
+
rb_funcall(optimizer_klass, rb_intern("optimize"), 1, signature);
|
124
|
+
}
|
125
|
+
}
|
126
|
+
|
127
|
+
rb_hash_aset(data, signature, ULONG2NUM(count));
|
128
|
+
}
|
129
|
+
break;
|
130
|
+
}
|
131
|
+
}
|
132
|
+
EOF
|
133
|
+
|
134
|
+
builder.c <<-'EOF'
|
135
|
+
void add_event_hook() {
|
136
|
+
rb_add_event_hook(prof_event_hook, RUBY_EVENT_CALL);
|
137
|
+
}
|
138
|
+
EOF
|
139
|
+
|
140
|
+
builder.c <<-'EOF'
|
141
|
+
void remove_event_hook() {
|
142
|
+
rb_remove_event_hook(prof_event_hook);
|
143
|
+
}
|
144
|
+
EOF
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
END { ZenOptimizer::stop_optimizing }
|
149
|
+
ZenOptimizer::start_optimizing
|
data/lib/zenprofile.rb
ADDED
@@ -0,0 +1,170 @@
|
|
1
|
+
require 'inline'
|
2
|
+
require 'singleton'
|
3
|
+
|
4
|
+
require 'pp'
|
5
|
+
|
6
|
+
class ZenProfiler
|
7
|
+
|
8
|
+
include Singleton
|
9
|
+
|
10
|
+
@@start = @@stack = @@map = nil
|
11
|
+
|
12
|
+
def self.start_profile
|
13
|
+
@@start = self.instance.time_now
|
14
|
+
@@stack = [[0, 0, [nil, :toplevel]], [0, 0, [nil, :dummy]]]
|
15
|
+
@@map = {"#toplevel" => [1, 0.0, 0.0, [nil, "#toplevel"]]}
|
16
|
+
self.instance.add_event_hook
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.stop_profile
|
20
|
+
self.instance.remove_event_hook
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.print_profile(f)
|
24
|
+
stop_profile
|
25
|
+
total = self.instance.time_now - @@start
|
26
|
+
if total == 0 then total = 0.01 end
|
27
|
+
@@map["#toplevel"][1] = total
|
28
|
+
data = @@map.values
|
29
|
+
data.sort!{|a,b| b[2] <=> a[2]} # TODO: change to sort_by
|
30
|
+
sum = 0
|
31
|
+
|
32
|
+
f.printf " %% cumulative self self total\n"
|
33
|
+
f.printf " time seconds seconds calls ms/call ms/call name\n"
|
34
|
+
for d in data
|
35
|
+
sum += d[2]
|
36
|
+
klass = d[3].first
|
37
|
+
meth = d[3].last.to_s
|
38
|
+
|
39
|
+
signature = if klass.nil?
|
40
|
+
meth
|
41
|
+
elsif klass.kind_of?(Class)
|
42
|
+
klass.to_s.sub(/#<\S+:(\S+)>/, '\\1') + "#" + meth
|
43
|
+
else
|
44
|
+
klass.class.name + "." + meth
|
45
|
+
end
|
46
|
+
|
47
|
+
f.printf "%6.2f %8.2f %8.2f %8d ", d[2]/total*100, sum, d[2], d[0]
|
48
|
+
f.printf "%8.2f %8.2f %s\n", d[2]*1000/d[0], d[1]*1000/d[0], signature
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
############################################################
|
53
|
+
# Inlined Methods:
|
54
|
+
|
55
|
+
inline(:C) do |builder|
|
56
|
+
|
57
|
+
builder.add_type_converter("rb_event_t", '', '')
|
58
|
+
builder.add_type_converter("ID", '', '')
|
59
|
+
builder.add_type_converter("NODE *", '', '')
|
60
|
+
|
61
|
+
builder.include '"ruby.h"'
|
62
|
+
builder.include '"node.h"'
|
63
|
+
|
64
|
+
builder.prefix "
|
65
|
+
static VALUE profiler_klass = Qnil;
|
66
|
+
static VALUE stack = Qnil;
|
67
|
+
static VALUE map = Qnil;
|
68
|
+
"
|
69
|
+
|
70
|
+
builder.c_raw <<-'EOF'
|
71
|
+
VALUE time_now() {
|
72
|
+
return rb_float_new(((double) clock() / CLOCKS_PER_SEC));
|
73
|
+
}
|
74
|
+
EOF
|
75
|
+
|
76
|
+
builder.c_raw <<-'EOF'
|
77
|
+
static void
|
78
|
+
prof_event_hook(rb_event_t event, NODE *node,
|
79
|
+
VALUE self, ID mid, VALUE klass) {
|
80
|
+
|
81
|
+
static int profiling = 0;
|
82
|
+
|
83
|
+
if (mid == ID_ALLOCATOR) return;
|
84
|
+
if (profiling) return;
|
85
|
+
profiling++;
|
86
|
+
|
87
|
+
if (NIL_P(profiler_klass))
|
88
|
+
profiler_klass = rb_path2class("ZenProfiler");
|
89
|
+
if (NIL_P(stack))
|
90
|
+
stack = rb_cv_get(profiler_klass, "@@stack");
|
91
|
+
if (NIL_P(map))
|
92
|
+
map = rb_cv_get(profiler_klass, "@@map");
|
93
|
+
|
94
|
+
switch (event) {
|
95
|
+
case RUBY_EVENT_CALL:
|
96
|
+
case RUBY_EVENT_C_CALL:
|
97
|
+
{
|
98
|
+
VALUE signature;
|
99
|
+
signature = rb_ary_new2(2);
|
100
|
+
rb_ary_store(signature, 0, klass);
|
101
|
+
rb_ary_store(signature, 1, ID2SYM(mid));
|
102
|
+
VALUE time = rb_ary_new();
|
103
|
+
rb_ary_push(time, time_now());
|
104
|
+
rb_ary_push(time, rb_float_new(0.0));
|
105
|
+
rb_ary_push(time, signature);
|
106
|
+
rb_ary_push(stack, time);
|
107
|
+
}
|
108
|
+
break;
|
109
|
+
case RUBY_EVENT_RETURN:
|
110
|
+
case RUBY_EVENT_C_RETURN:
|
111
|
+
{
|
112
|
+
VALUE now = time_now();
|
113
|
+
VALUE tick = rb_ary_pop(stack);
|
114
|
+
|
115
|
+
VALUE signature = rb_ary_entry(tick, -1);
|
116
|
+
|
117
|
+
VALUE data = Qnil;
|
118
|
+
st_lookup(RHASH(map)->tbl, signature, &data);
|
119
|
+
if (NIL_P(data)) {
|
120
|
+
data = rb_ary_new();
|
121
|
+
rb_ary_push(data, INT2FIX(0));
|
122
|
+
rb_ary_push(data, rb_float_new(0.0));
|
123
|
+
rb_ary_push(data, rb_float_new(0.0));
|
124
|
+
rb_ary_push(data, signature);
|
125
|
+
rb_hash_aset(map, signature, data);
|
126
|
+
}
|
127
|
+
|
128
|
+
rb_ary_store(data, 0, ULONG2NUM(NUM2ULONG(rb_ary_entry(data, 0)) + 1));
|
129
|
+
|
130
|
+
double cost = NUM2DBL(now) - NUM2DBL(rb_ary_entry(tick, 0));
|
131
|
+
|
132
|
+
rb_ary_store(data, 1, rb_float_new(NUM2DBL(rb_ary_entry(data, 1))
|
133
|
+
+ cost));
|
134
|
+
rb_ary_store(data, 2, rb_float_new(NUM2DBL(rb_ary_entry(data, 2))
|
135
|
+
+ cost
|
136
|
+
- NUM2DBL(rb_ary_entry(tick, 1))));
|
137
|
+
|
138
|
+
VALUE toplevel = rb_ary_entry(stack, -1);
|
139
|
+
VALUE tl_stats = rb_ary_entry(toplevel, 1);
|
140
|
+
long n = NUM2DBL(tl_stats) + cost;
|
141
|
+
rb_ary_store(toplevel, 1, rb_float_new(n));
|
142
|
+
}
|
143
|
+
break;
|
144
|
+
}
|
145
|
+
profiling--;
|
146
|
+
}
|
147
|
+
EOF
|
148
|
+
|
149
|
+
builder.c <<-'EOF'
|
150
|
+
void add_event_hook() {
|
151
|
+
rb_add_event_hook(prof_event_hook,
|
152
|
+
RUBY_EVENT_CALL | RUBY_EVENT_RETURN |
|
153
|
+
RUBY_EVENT_C_CALL | RUBY_EVENT_C_RETURN);
|
154
|
+
}
|
155
|
+
EOF
|
156
|
+
|
157
|
+
builder.c <<-'EOF'
|
158
|
+
void remove_event_hook() {
|
159
|
+
rb_remove_event_hook(prof_event_hook);
|
160
|
+
}
|
161
|
+
EOF
|
162
|
+
|
163
|
+
end
|
164
|
+
|
165
|
+
end
|
166
|
+
|
167
|
+
END {
|
168
|
+
ZenProfiler::print_profile(STDOUT)
|
169
|
+
}
|
170
|
+
ZenProfiler::start_profile
|
data/misc/factorial.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
#!/usr/local/bin/ruby -w
|
2
|
+
|
3
|
+
class Factorial
|
4
|
+
|
5
|
+
def factorial(n)
|
6
|
+
f = 1
|
7
|
+
n.downto(2) { |x| f *= x }
|
8
|
+
return f
|
9
|
+
end
|
10
|
+
|
11
|
+
end
|
12
|
+
|
13
|
+
f = Factorial.new()
|
14
|
+
|
15
|
+
max = ARGV.shift || 1000000
|
16
|
+
max = max.to_i
|
17
|
+
|
18
|
+
tstart = Time.now
|
19
|
+
|
20
|
+
(1..max).each { |m| n = f.factorial(5); }
|
21
|
+
|
22
|
+
tend = Time.now
|
23
|
+
|
24
|
+
total = tend - tstart
|
25
|
+
avg = total / max
|
26
|
+
printf "Iter = #{max}, T = %.8f sec, %.8f sec / iter\n", total, avg
|
data/misc/find_c_methods
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
#!/usr/local/bin/ruby -w
|
2
|
+
|
3
|
+
require 'pp'
|
4
|
+
|
5
|
+
class PP
|
6
|
+
module PPMethods
|
7
|
+
alias old_pp_hash pp_hash
|
8
|
+
def pp_hash(obj)
|
9
|
+
group(1, '{', '}') {
|
10
|
+
seplist(obj, nil, :each_pair) {|k, v|
|
11
|
+
group {
|
12
|
+
pp k
|
13
|
+
text ' => '
|
14
|
+
group(1) {
|
15
|
+
breakable ''
|
16
|
+
pp v
|
17
|
+
}
|
18
|
+
}
|
19
|
+
}
|
20
|
+
}
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# KLASSES[klass] = c_name
|
26
|
+
KLASSES = {}
|
27
|
+
|
28
|
+
# METHODS[klass][ruby_name] = c_name
|
29
|
+
METHODS = Hash.new { |h,k| h[k] = {} }
|
30
|
+
|
31
|
+
Dir.chdir ARGV.shift do
|
32
|
+
Dir['*.c'].each do |c_file|
|
33
|
+
File.open c_file do |fp|
|
34
|
+
fp.each_line do |line|
|
35
|
+
case line
|
36
|
+
when /([^ ]+)\s+=\srb_define_class\("([^"]+)"/ then
|
37
|
+
KLASSES[$1] = $2
|
38
|
+
when /rb_define_method\(([^,]+),\s+"([^"]+)",\s+([^,]+), (-?\d+)/ then
|
39
|
+
klass = KLASSES[$1]
|
40
|
+
METHODS[klass][$2.intern] = $3, $4.to_i
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
print "RUBY_C_METHOD_MAP = "
|
48
|
+
pp METHODS
|
49
|
+
|
@@ -0,0 +1,62 @@
|
|
1
|
+
#!/usr/local/bin/ruby -w
|
2
|
+
|
3
|
+
# Refax automatic refactoring system by Rudi Cilibrasi (cilibrar@gmail.com)
|
4
|
+
# A simple example test of the new ParseTree library.
|
5
|
+
#
|
6
|
+
# This program is meant to suggest possible refactoring opportunities
|
7
|
+
#
|
8
|
+
# to convert loops of the form
|
9
|
+
# stmt A
|
10
|
+
# stmt B
|
11
|
+
# stmt C
|
12
|
+
# while cond
|
13
|
+
# stmt A
|
14
|
+
# stmt B
|
15
|
+
# stmt C
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# to
|
19
|
+
#
|
20
|
+
# begin
|
21
|
+
# stmt A
|
22
|
+
# stmt B
|
23
|
+
# stmt C
|
24
|
+
# end while cond
|
25
|
+
#
|
26
|
+
# to use this system, just put
|
27
|
+
#
|
28
|
+
# require 'fixloops'
|
29
|
+
# at the bottom of your ruby source file after requiring all classes to check
|
30
|
+
#
|
31
|
+
# ParseTree notes:
|
32
|
+
# 1) How can you distinguish while loops with preconditions
|
33
|
+
# versus those with postconditional guards? It looks like the parse tree
|
34
|
+
# is showing them the same at this moment.
|
35
|
+
# 2) Trying to parse_tree(Object) throws a nil ptr
|
36
|
+
|
37
|
+
|
38
|
+
# And here is an example hastily.rb that can be refactored using it:
|
39
|
+
# Example Ruby program to test automatic refactoring engine based on ParseTree
|
40
|
+
# by Rudi Cilibrasi (cilibrar@gmail.com)
|
41
|
+
|
42
|
+
class HastilyWritten
|
43
|
+
def keepgoing() rand(2) == 0 end
|
44
|
+
def doSomethingWeird() puts "zzzzz" end
|
45
|
+
def weirdfunc
|
46
|
+
puts "This is a weird loop"
|
47
|
+
doSomethingWeird()
|
48
|
+
while keepgoing
|
49
|
+
puts "This is a weird loop"
|
50
|
+
doSomethingWeird()
|
51
|
+
end
|
52
|
+
end
|
53
|
+
def finefunc
|
54
|
+
begin
|
55
|
+
puts "This is a weird loop"
|
56
|
+
doSomethingWeird()
|
57
|
+
end while keepgoing
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# Here is the line you need to use Refax automatic refactoring system
|
62
|
+
require 'fixloops'
|