ruby picture

RCR 248: 'once' operator to evaluate an expression only once

Submitted by Eludias (Sun Apr 18 09:04:40 UTC 2004)

Abstract

Objects which are constant during a script only need to be evaluated once. The best place for this is at the object definition itself. This saves time and/or memory, and keeps local objects local.

Problem

When a constant object is reused a lot of times in indepenent contexts, a place to cache this object must be created to be able to share this constant object. For regular expressions this is already arranged with the 'once' modifier: %r{expression}o compiles the regular expression only once, and next time the cached version is used. This is also useful for other objects because of space and/or time concerns: for example, a string can be shared. This can be worked around by using named objects which act as the cache in the class or global scope, but this is ugly.

Proposal

Add the 'once' keyword/method to indicate a part of the parse-tree which should be evaluated only once just like in the regular expression case. For this, we need something with a local scope without having a name: an anonymous instance (just like the already existing anonymous classes).

The 'once' keyword would take one expression which should be evaluated at most once. For example,

%r{something}o
is equal to
once(%r{something})
.

Space-saving example:


    
  class LittleObject
    attr_reader :aspect1
    def initialize(aspect1)
      @aspect1 = once("defaultValue")
      @aspect1 = aspect1 if aspect1 != nil
    end
  end

    

When thousands of LittleObjects are created with a default 'aspect1', this saves space since all instances of LittleObject which do not change aspect1 still refer to the default (unnamed) value. The current (inelegant) solution would be:


    
      ...
      DEFAULTVALUE = "defaultValue" if not defined? DEFAULTVALUE
      @aspect1 = DEFAULTVALUE
      ...

    

...but this requires given something a name which does not need a name.

Time saving example:


    
   def logLine(line)
     puts "#{once(`uname --nodename`)} #{line}"
   end

    

This will only call 'uname' once. Currently this can be implemented as:


    
   def logLine(line)
     $nodename = `uname --nodename` if not $nodename
     puts "#{$nodename} #{line}"
   end

    

..which isn't too bad, but creates a variable with a much larger scope (global or a constant) than needed. In this last example, the naming is not a problem.

Code saving example:


    
   class StringCache
     def Cached(str)
       cache = once({})
       cache[str] = str if not cache[str]
       cache[str]
     end
   end

    

Analysis

This problem requires a language-level change since only the parser can cache depending on the position in the script and one cannot pass a parsed expression without evaluating it first in the given context.

The infrastructure is already in place since it uses the same construct as is already in use for the regular expressions.

The 'once' operator is a small add-on which can be used in a lot of places to optimise a ruby script.

Implementation

Implementation is not possible in pure Ruby, although in pseudo-code it would be (non-optimal):


    
  def once(expression)
    $onceCache = {} if not $onceCache
    res = $onceCache[[expression, stack[-1]]
    if not res
      $onceCache[[expression, stack[-1]] = eval(expression, stack[-1].context)
    else
      res
    end
  end

  
ruby picture
Comments Current voting

I know this already exists in the case of regular expressions, but I have to say I'd rather not see 'once' generally in the language. I like my high-level languages high-level :-) The idea of adding, in effect, compiler hints to Ruby doesn't appeal to me.

Also, I think you can achieve all of what you need already in various ways -- sometimes, admittedly, more verbose than "once", but not unreasonably so. Class objects are a good place to stash things. For example:

class A
  def self.default_a1
    @a1 ||= "default value"
  end
  attr_reader :aspect1
  def initialize(aspect1=nil)
    @aspect1 = aspect1 || A::default_a1
  end
end

I actually prefer having this laid out programmatically like this, though tastes may vary.

-- David Black


Is this some feature from perl ? In contrast to perl, it's quite good to have a meta programming language like ruby. Just a few lines:


def once(exp,file=nil,line=nil,bind=nil)
  $once ||= {}
  $once[[exp,file,line]] ||= eval(exp,bind)
end
once '/regex(expression)?/'
class A
  def A.methods
    once('methods', __FILE__, __LINE__, binding)
  end
end

But I think, you don't need this very often. Ruby has limited meta access compared to LISP and this is a good thing. Furthermore Perfomance tuning complicates the language.

Besides, two binding objects created at the same place are not equal. I think, it would be better if binding == binding would result in true. Any opinions ?

-- Matthias Georgi


Sorry, I'm losing the flow here. Is what "some feature from Perl"?

-- David Black


Was not important, I imagined, "evaluating once" was a Perl feature, but I was mistaken. Now a better once...


def once
  $once ||= {}
  $once[caller.first] ||= yield
end
def test_once
  world = :world
  once { "Hello #{world}" }
end
test_once == test_once => true

works only for one once at one line.

-- Matthias Georgi


I agree it looks like premature optimisation, but it is not only meant as an optimisation. It is also about having an persistent anonymous object.

I didn't know the '||=' trick mentioned above, and looks quite useful in itself: one could see the once() as a way to indicate what you're doing instead of obfuscating it with '||='; a '$cache = once({})' looks more clear to me than a '$cache ||= {}'. [However, I think I'll just over turn to the dark side and will use the '||=' construct myself :-]

However, for optimisation purposes (indeed, not the most important one), the last solution does not suffice time-wise:

def once; $once ||= {}; $once[caller.first] ||= yield; end
class SmallObject
  def initialize
    $myValue ||= "My value"
    $myValue = once { "My value" }; @val = $myValue
  end
end
sos = []; t = Time.now for i in 1..1000000; sos.push(SmallObject.new); end puts Time.now - t

...takes 10x (measured) as much time than the '||=' version, so that's not really an option for a lot of small objects, because of calling a method, constructing an array and throwing it all away, yielding, etc. [Yeah, I know, micro-benchmarking, don't do it, etc...]

Therefore I proposed to add it to the language: It is simple to implement, it's clear what the user meant with it and it's an optimisation when implemented in the language itself.

To see that it is simple to implement, see eval.c for the current implementation of the regular-expression case. The parse-tree is changed during the first evaluation so the next time the literal can be used:


          
              case NODE_DREGX_ONCE:     /* regexp expand once */
                result = rb_reg_new(RSTRING(str)->ptr, RSTRING(str)->len,
                                    node->nd_cflag);
                nd_set_type(node, NODE_LIT);
                node->nd_lit = result;
                break;

          

This is also the right place: a 'once' should not be dependent on a line-number, but the place in the code, which equals the location in the parse tree (which is walked in eval.c).

And about bindings not being equal: {}.eql?({}) is already false (quite logical). {} == {} is true indeed, but the same holds for once() expressions if the objects represents the same thing.

But still thanks for the '||=' operator! (...time to impress my friends with it ;-)

Rutger.


||= is not ugly, but a common ruby idiom. After benchmarking, I was tempted to find a solution and finally I've got a little hack.


$once = {}
class SmallObject1
  def initialize
    @val = once("My value")
  end
end
class SmallObject2
  def initialize    
    @val = ($myValue ||= "My value")
  end
end
eval(File.read(__FILE__).gsub(/eval/,'#').gsub(/once\((.*?)\)/, '($once[__LINE__] ||= \1)'))
require 'benchmark' include Benchmark
bm do |x|
  x.report { 100000.times { SmallObject1.new } }
  x.report { 100000.times { SmallObject2.new } }
end

Probably not really useful for you, but basically the question to ask is, if LISP like macros should be added to Ruby ...

-- Matthias Georgi


I'd like to have "once" in Ruby, but in a totally different way:


          
  class MyClass
     def do_heavy_calculation
          # some calculation finally returning something
     end
     once :do_heavy_calculation
   end   obj = MyClass.new
   a = obj.do_heavy_calculation # Here the value is calculated and stored
                                # somewhere in obj
   b = obj.do_heavy_calculation # Here the value is simply looked up 
It's coded acting like this or similar somewhere in the Pickaxe book, but I don't know anymore where exactly. -- Malte
          


I've done an implementation for this other kind of "once" but I call it cacheable. See message on ruby-talk. -- joar


Strongly opposed 2
Opposed 6
Neutral 0
In favor 1
Strongly advocate 0
ruby picture
If you have registered at RCRchive, you may now sign in below. If you have not registered, you may sign up for a username and password. Registering enables you to submit new RCRs, and vote and leave comments on existing RCRs.
Your username:
Your password:

ruby picture

Powered by .