From: Denis de Bernardy Date: 2011-07-12T17:13:33+09:00 Subject: [ruby-core:38015] [Ruby 1.9 - Feature #3917] [proposal] called_from() which is much faster than caller() Issue #3917 has been updated by Denis de Bernardy. I'm not making much sense of the japanese in this ticket. Is this (or #1906, which also looks neat) anything that might make it into ruby 1.9.3? I was wondering how to get the calling file's name earlier today without resorting to caller() -- which yields an unnecessarily large string array. As an aside, there's this sender gem written in C here, in the meanwhile: https://github.com/Asher-/sender ---------------------------------------- Feature #3917: [proposal] called_from() which is much faster than caller() http://redmine.ruby-lang.org/issues/3917 Author: makoto kuwata Status: Assigned Priority: Normal Assignee: Koichi Sasada Category: core Target version: 1.9.x =begin I propose to introduce Kernel#called_from() which is similar to caller() but much faster than it. Background ---------- There are some cases to want to know from where current method is called. In this case, Kernel#caller() is used. But Kernel#caller() has performance issues for these cases. * caller() retrieves entire stack frame. It is too heavy. * caller() returns an array of "filename:linenum in `method'" string. User must parse it and retrieve filename and linenum by rexp. It is also very heavy weight task. Therefore I propose Kernel#called_from() which is very light weight compared to caller(). A certain benchmark shows that called_from() is more than 20 times faster tan caller(). ������������������������������������������������������������������������������������������������������ ��������������������������� Kernel#caller() ������������������ ��������� Kernel#caller() ��������������������������������������������������������������������������� * caller() ��������������������������������������������������������������������������������������� * caller() ��� "���������������:��������� in `���������������'" ��������������������������������������� ��������������������������������������������������������������������������������������������������������� ������������������������ ���������������Kernel#called_from() ��������������������������������������� ��������������������� caller() ��������������������������������������������������������������� called_from() ��� caller() ������������20������������������ Spec ----- call-seq: called_from(start=1) -> array or nil Returns file name, line number, and method name of the stack. The optional _start_ parameter represents the number of stack entries to skip. Returns +nil+ if _start_ is greater than the size of current execution stack. Raises ArgumentError if _start_ is negative value. Example code ------------ # example.rb 1: def f1() 2: f2() 3: end 4: def f2() 5: f3() 6: end 7: def f3() 8: p called_from() #=> ["example.rb", 5, "f2"] 9: p called_from(0) #=> ["example.rb", 9, "f3"] 10: p called_from(1) #=> ["example.rb", 5, "f2"] 11: p called_from(2) #=> ["example.rb", 2, "f1"] 12: p called_from(3) #=> ["example.rb", 15, "
"] 13: p called_from(4) #=> nil 14: end 15: f1() Use Case -------- Case 1: logging method def log_info(message) filename, linenum, _ = called_from() # !!! @logger.info "#{filename}:#{linenum}: #{message}" end Case 2: debug print def debug(message) filename, linenum, _ = called_from() # !!! $stderr.puts "*** DEBUG: #{filename}:#{linenum}: #{message}" end Case 3: deprecation message def send(*args) filename, linenum, _ = called_from() # !!! msg = "`send()' is deprecated. use `__send__()' instead." msg << " (file: #{filename}, line: #{linenum})" $stderr.puts "*** warning: #{msg}" __send__(*args) end Case 4: ActiveSupport::Testing::Pending module ActiveSupport::Testing::Peding def pending(description = "", &block) : #caller[0] =~ (/(.*):(.*):in `(.*)'/) # original #@@pending_cases << "#{$3} at #{$1}, line #{$2}" # original #print "P" # original filenemae, linenum, method = called_from() # !!! @@pending_cases << "#{method} at #{filename}, line #{linenum}" print "P" : end end Case 5: activesupport/lib/active_support/core_ext/module/delegation.rb class Module def delegate(*methods) : #file, line = caller.first.split(':', 2) # original #line = line.to_i # original file, line, _ = called_from() # !!! : module_eval(<<-EOS, file, line - 5) : end end Case 6: caching helper for template system def cache_with(key) data, created_at = @_cache_store.get(key) filename, = called_from() # !!! ## if template file is newer than cached data then clear cache. ## (performance is very important in this case.) if created_at < File.mtime(filename) data = nil @_cache_store.del(key) end ## if data.nil? len = @_buf.length yield data = @_buf[len..-1] @_cache_store.set(key, data) else @_buf << data end nil end ## in template file <% cache_with("orders/#{@order.id}") do %>

Order ID: <%=h @order.id %>

Customer: <%=h @order.customer.name %>

<% end %> Benchmark --------- Attached benchmark shows that called_from() is much faster than caller(). This is very important for logging or template timestamp check. $ ./ruby -s bench.rb -N=100000 user system total real caller()[0] 1.890000 0.010000 1.900000 ( 1.941812) caller()[0] (retrieve) 2.190000 0.010000 2.200000 ( 2.225966) called_from() 0.100000 0.000000 0.100000 ( 0.102810) called_from() (retrieve) 0.100000 0.000000 0.100000 ( 0.102133) Another Solutions ----------------- Adding new gobal function may be refused. The followings are another solutions instead of new global function. * Extend caller() to take 'count' parameter. For example: start = 1 count = 1 caller(start, count) #=> ["filename:linenum in `method'"] * Extend caller() to take 'conbine' flag. For example: start = 1 count = nil conbine = false caller(start, count, conbine) #=> [["filename", linenum, "method"], # ["filename", linenum, "method"], # .... ] * Add new standard library 'called_from.so' instead of Kernel#called_from(). ��������������������������������������������������������������������������������������� ������������������caller()���������������called_from()������������������������������������ ������������������������������������ ������������ Kernel#called_from() ������������������ called_from.so ��������������� ��������������������������� Note ---- * I tried to implement the above solutions, but failed because vm_backtrace_each() seems to search stack frames in the reverse order of what called_from() requires. * I can implement called_from() as user library in Ruby 1.8. http://rubygems.org/gems/called_from It is allowed to access to stack frame in Ruby 1.8, but no in 1.9. This is why I submit this propose. * ���������������another solutions���������������������������������called_from() ������ ������������������������������������������������������������������vm_backtrace_each() ��� ������������������������������������������������������������������������������������ * Ruby 1.8 ��������������������������������������������������������������������������������������� ��������������������������������������� http://rubygems.org/gems/called_from ������1.9������������������������������������������������������������������������������������������ ������������������������������������������������������������������������ =end -- http://redmine.ruby-lang.org