ZenHacks 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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'
|