4
$\begingroup$

I am able to evaluate a held expression that I have put on a list by:

ReleaseHold[expressionList[[1]]]

and

expression1 := ReleaseHold[expressionList[[1]]];
expression1

Using expression1 is more efficient than ReleaseHold[expressionList[[1]]], but I loose the ability to use it in Map (or so I think). I would like to be able to use the held expression directly from a list, i.e. expressionList[[1]] .

Any suggestions?

A test harness follows. You can change the expressions being tested see AddExpression[1];, which is currently 1 to Pause[$TimeUnit], Range[1, 1000], ... .


SetAttributes[AddExpression, HoldAll];
AddExpression[expression_] := AppendTo[expressionList, Hold[expression]];

EvaluateExpressions[] :=
 (
  expression1 := ReleaseHold[expressionList[[1]]];
  expression2 := ReleaseHold[expressionList[[2]]];

  totalRunTimeDo = AbsoluteTiming[
     Do[(expression1; expression2;), {iterations}];
     ][[1]];

  Print["Do      total: ", totalRunTimeDo, "   per iteration: ", 
   totalRunTimeDo/ iterations];

  (* -------------------------------------------------*)

  totalRunTimeDoMap = AbsoluteTiming[
     Do[Map[ReleaseHold[#] &, expressionList], {iterations}];
     ][[1]];

  Print["DoMap   total: ", totalRunTimeDoMap, "   per iteration: ", 
   totalRunTimeDoMap/ iterations];
  )

iterations = 10000000;
expressionList = {};
AddExpression[1];
AddExpression[1];
EvaluateExpressions[];

Results:

Do      total: 12.526729   per iteration: 1.2526729*10^-6
DoMap   total: 19.018290   per iteration: 1.9018290*10^-6

Obviously, you can see why I want to pull it directly from the list.

Background

The above test harness was boiled down from something I was using to benchmark code.

iterations = 10000000;
expressionList = {Hold[1], Hold[1]};

I started here:

expressionTimingsByIteration =
  Map[Map[AbsoluteTiming[ReleaseHold[#]][[1]] &, expressionList] &,
   Range[1, iterations]];
iterationsTimingsByExpression = Transpose[expressionTimingsByIteration];

Refactored to this:

iterationsTimingsByExpression =
  Map[
   (
     expression := ReleaseHold[#];
     Map[AbsoluteTiming[expression][[1]] &, Range[1, iterations]] 
     ) &
   , expressionList];

However evaluating code whose AbsoluteTiming < $TimeUnit is prone to error. Which I refactored for those cases to:

totalTimingByExpression =
  Map[
   (
     expression := ReleaseHold[#];
     AbsoluteTiming[Do[expression, {iterations}]][[1]]
     ) &, expressionList];
totalTimingByExpression
$\endgroup$
5
  • 1
    $\begingroup$ Related question: stackoverflow.com/questions/6254538/… $\endgroup$ Commented Mar 9, 2012 at 10:40
  • $\begingroup$ @LeonidShifrin Sorry about the duplicate post. Is there away to search stackoveflow and mathematica.se at the same time? Or are there plans to move/link the Mathematica posts on stackoverflow to mathematica.se? While having a separate mathematica.se makes sense it seems. However having two sites for Mathematica only increases the likely hood of future duplicate questions. $\endgroup$
    – mmorris
    Commented Mar 9, 2012 at 14:57
  • $\begingroup$ There is no reason to be sorry. It is not always easy to find what you need, and in any case the question is not duplicate because SO and Mathematica SE are different sites. Eventually some questions get migrated from SO to this site. Right now, this site is by far the most active Mathematica resource on SE, while the SO Mathematica tag remains mostly a repository of some already answered questions. $\endgroup$ Commented Mar 9, 2012 at 15:28
  • $\begingroup$ @LeonidShifrin Hmmmm, I am trying to parse you statement, "SO and Mathematica SE are different sites". Sites as in databases, but not general purpose? I thought, please correct me if I am wrong (< 1 year on SO and < 1 moth here), SO is general purpose for questions(discussions) on topics that don't have a specialized SE site. In theory the purpose of SO w/[mathematica] == Mathematica SE, where Mathematica SE is specific to the Mathematica domain which allows for categorization via tags within that domain. $\endgroup$
    – mmorris
    Commented Mar 9, 2012 at 15:53
  • 1
    $\begingroup$ Within SO Mathematica tag, only programming questions were on topic, strictly speaking. Also, it was just one tag within a much larger SO infrastructure. The purpose of the two sites is similar, but they are different sites in terms of infrastructure, moderation, rules, etc. You can read discussions which were accompanying the SE Mathematica proposal on Area51, to better see the difference - it was extensively discussed there. $\endgroup$ Commented Mar 9, 2012 at 16:21

2 Answers 2

7
$\begingroup$

What costs you so much time is your unnecessary packaging of ReleaseHold into an anonymous function. ReleaseHold[#]& is semantically identical to ReleaseHold but much more time expensive. If you replace it with ReleaseHold, the map version is only slightly slower than the direct version (and I guess that's because of the Map logic which has to work with lists of arbitrary length).

Here's what I get after removing the unnecessary anonymous function:

Do      total: 22.849438   per iteration: 2.2849438*10^-6
DoMap   total: 24.159825   per iteration: 2.4159825*10^-6
$\endgroup$
1
  • $\begingroup$ Thanks for that, I will pay more attention to not using anonymous functions. That narrowed the performance difference significantly. Do total: 12.386823 DoMap total: 19.275236 DoMapDirect total: 13.860921 I had a previous version that used two Maps instead of a Do and a Map MapMap total: 21.982573. That is when I learned to use Do in place of Map when just iterating over a value not used in the loop. $\endgroup$
    – mmorris
    Commented Mar 9, 2012 at 18:29
7
$\begingroup$

I think that, first, there is a simpler way to do what you intend, and second, the benchmarks and timing results will significantly depend on the total length of the expression list. For small expression lists, a large part of the total running time is not related to expressions evaluation, but to the overall overhead of things like CompoundExpression etc.

First, here is how I'd do this:

ClearAll[$expressionList, addExpression, clearExpressions];
$expressionList = Hold[];
SetAttributes[addExpression, HoldAll];
addExpression[expression_] :=
     $expressionList = Append[$expressionList, Unevaluated[expression]];
clearExpressions[] := $expressionList = Hold[];

Basically, the idea is to store all expressions in unevaluated form in a single Hold (you can use HoldComplete if you wish). Then, evaluation of a given held expression is just a matter of extracting the relevant part, while evaluating them all is just as easy as applying ReleaseHold to $expressionList.

I will now populate $expressionList with 1000 unevaluated expressions:

iterations = 20000;
clearExpressions[];
Do[With[{i = i}, addExpression[i*i]], {i, 1, 1000}];

(I had to reduce the number of interations accordingly). Now, sequential evaluation is somewhat faster (about 1.5 times) than in your constructs, both for loops and Scan (can not use Map here however, won't evaluate inside held expression by itself)

AbsoluteTiming[
    Do[Do[$expressionList[[i]],{i,1,Length[$expressionList]}],{iterations}];][[1]]
AbsoluteTiming[Do[Scan[#&,$expressionList],{iterations}];][[1]]

(* 
  ==> 11.3134766
  ==> 10.2519531
*)

However, evaluating expressions "wholesale" is even much faster:

AbsoluteTiming[Do[ReleaseHold@$expressionList;, {iterations}];][[1]]

(* 
  ==> 3.2216797
*)
$\endgroup$
5
  • $\begingroup$ I like this solution. The timing numbers are impressive too. I applied your solutions to my original test, the results are: 13.047372, 9.770802, and 5.494147. The second implementation, Do Scan, seems most applicable to my intended application, compared to my contrived example above. I could have had my cake and eat it too if Scan only returned results of each call like Map does. $\endgroup$
    – mmorris
    Commented Mar 9, 2012 at 18:14
  • 1
    $\begingroup$ @mmorris If you use Map, then you could just as well use my last suggestion ReleaseHold@$expressionList, couldn't you? Or do you want to evaluate only some subset of elements in your held list, and collect the results of that? $\endgroup$ Commented Mar 9, 2012 at 18:45
  • $\begingroup$ The example code above is intended to benchmark benchmarking code. Original benchmark code: expressionTimingsByIteration = Map[Map[AbsoluteTiming[ReleaseHold[#]][[1]] &, expressionList] &, Range[1, iterations]]; iterationsTimingsByExpression = Transpose[expressionTimingsByIteration]; Refactored to this: iterationsTimingsByExpression = Map[ ( expression := ReleaseHold[#]; Map[AbsoluteTiming[expression][[1]] &, Range[1, iterations]] ) & , expressionList]; iterationsTimingsByExpression ... $\endgroup$
    – mmorris
    Commented Mar 10, 2012 at 1:52
  • $\begingroup$ ... However evaluating code whose AbsoluteTiming < $TimeUnit is prone to error. Which I refactored for those cases to: totalTimingByExpression = Map[ ( expression := ReleaseHold[#]; AbsoluteTiming[Do[expression, {iterations}]][[1]] ) &, expressionList]; totalTimingByExpression Which I was looking to eliminate what celtschk pointed out. $\endgroup$
    – mmorris
    Commented Mar 10, 2012 at 2:05
  • $\begingroup$ I'll post those in my question so they format better. $\endgroup$
    – mmorris
    Commented Mar 10, 2012 at 2:20

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.