Sign in to follow this  
gsgeek

Unity When are method arguments evaluated in Mono's C#?

Recommended Posts

Hello all,

 

First of all, sorry if this is kind of a dumb question, but I can't seem to find an answer for it.

Let's say that in a c# project (in my case, a c# script inside a Unity3d game, if that changes something), I have  two methods defined as follows (in pseudo code):

int x;
int y;
​int z;
​int u;
​int v; // these ints are given values depending on the evolution of each game run, so they are not known at compile time.
bool intToBool( int x ) { /*some complex block that computes a bool from the passed argument*/}
int gdc ( int x ) { /* method that takes a int value dependent on the game run 
-that is, not known at compile time- 
and computes another int from it thorugh very complex and expensive calculations*/}

void SomeStuff(int x){} //auxiliar methods called inside myMethod
void OtherStuff(int x){}
void MoreStuff(int x){}

void myMethod(int a, int b, int c, int d, int e){
  /* here goes code that references the arguments by name multiple name, for example:*/
  SomeStuff(a); OtherStuff(b); MoreStuff(c);
  SomeStuff(d); OtherStuff(e); MoreStuff(a);
  SomeStuff(b); OtherStuff(c); MoreStuff(d);
  SomeStuff(e); OtherStuff(a); MoreStuff(b);
  SomeStuff(c); OtherStuff(d); MoreStuff(e);
}
  
 
void myMethodBis(int a, int b, int c, int d, int e){
  if( intToBool( a )) { /*execute statemens*/ }
  else if( intToBool( b )) { /*execute statemens*/ }
  else if( intToBool( x )) { /*execute statemens*/ }
  else if( intToBool( d )) { /*execute statemens*/ }
  else if( intToBool( e )) { /*execute statemens*/ }
}

Further suppose that these methods are going to be called with arguments that are not known at compile time (e.g. because they depend on the evolution of the particular run on the game), like this:

myMethod( gdc(x), gdc(y), gdc(z), gdc(u), gdc(v) );

myMethodBis( gdc(x), gdc(y), gdc(z), gdc(u), gdc(v) );

So, my question is, when are gdc(x)-gdc(v), etc. evaluated?

Are they first computed at the start of the method calls and saved as local variables internally?

Or are they computed each time they are referenced in the method's block?

So, for example, if gdc(x) - gdc(v) are going to be computed regardless of wether they are called in the method's body, and gdc is very computationally intensive, I should rather inline MyMethodBis to save four of these computation.

On the other hand, if they are computed not when the method is invoked but when they are used inside the method body, In myMethod I should first save a-e as local variables so I'm only computing them once, and use the local variables several times through the method's body.

(Bear in mind that the main aim of my question is to know how is the code executed internally, rather than having to do the optimisations I meantion. That has been only the problem that has sparkled the theoretical question).

Thank you in advance for any insight you can give regarding this topic.

Share this post


Link to post
Share on other sites

So, my question is, when are gdc(x)-gdc(v), etc. evaluated? Are they first computed at the start of the method calls and saved as local variables internally?

It's fully defined in the C# language specification. I grabbed a random version from the Interwebs, it seems old, but such basic concepts never change anyway:

 

https://msdn.microsoft.com/en-us/library/aa691335(v=vs.71).aspx

During the run-time processing of a function member invocation (Section 7.4.3), the expressions or variable references of an argument list are evaluated in order, from left to right, as follows:

   ...

So it promises to start with "gdc(x)" and end with "gdc(v)" evaluation (this is relevant if the gcd calls share some data that they modify).

There are lots of complications with reference variables, and null values etc, but that all doesn't apply with "int"

 

The actual call is defined in Section 7.4.3, apparently, which is
https://msdn.microsoft.com/en-us/library/aa691340(v=vs.71).asp

  • The argument list is evaluated as described in Section 7.4.1.
  • M is invoked.

 

Again all kinds of cases for all the different kinds of functions, but the above is everywhere in some way. First evaluate the arguments, then call the method.

Of course this makes sense, the function header (technically known as "function signature") says to expect a number of integers, so if you have expressions instead, they first need to be converted to integers.

 

The above is what any C# compiler will do, at conceptual level. That means, no matter what code you write, or whatever code the compiler actually generates or executes, results that you get must be explainable from the above description. If not, it's a compiler bug.

I deliberately said "whatever code the compiler actually generates or executes" there. The compiler is free to change anything wrt code generation that it likes, except it must result in answers that can be explained from the description in the language specification. The compiler thus may inline your method call, or may conclude that it never needs to evaluate "gcd(v)" in some case. It may also decide to expand "gcd(x)" and compute whenever it is needed. All that and more is allowed, the answer just must be the same as a compiler that literally implemented the language specification.

 

Why doesn't the language description explain what the compiler really does?

Reality is, compilers change all the time. People are constantly looking for, and finding, new ways to compute things faster. Compilers quite literally not do what they claim to do. They do something else instead, which is generally performing better, but you never know it does something else, since the result cannot be distinguished from the description of the language reference.

This is why the language description is describing the execution at conceptual level. For a programmer, it's a solid foundation. If you follow the rules of the language specification, your program will now and in the future continue to work, no matter what weird tricks the compiler authors pull from their hat.

For the compiler authors, it gives freedom. They can change anything they like, as long as the result with respect to the language specification remains the same. All programs ever written will continue to work.

 

So the answer to your question is "don't know, and that's good". Tomorrows compiler may do things differently anyway, and that's fine.

Share this post


Link to post
Share on other sites

 [...]

 

 

Thank you very much for the answer! Now that begs new questions, like how come operators can be "shortcut" like && (i.e. operands are not evaluated before the body of the operator is executed) and methods seemingly can not. But then again my internal knowledge of c# operators themselves is lacking. I'll have to deep into the language specification further!

Share this post


Link to post
Share on other sites

how come operators can be "shortcut" like && (i.e. operands are not evaluated before the body of the operator is executed) and methods seemingly can not

 

Why would you say that?

 

Parameters are different than operators.  The && operator is evaluated first, returning true or false, and that true or false is evaluated as a parameter.  To evaluate && it looks at the first, then looks at the second. 

Share this post


Link to post
Share on other sites

Short-circuit evaluation is only available for relevant boolean operators, && and || (the XOR, ^, operator isn't, since you always need to evaluate both operands anyway). You could say those operators are somewhat 'special' yes, but is definitely different from what you're expecting. The conditional operators can easily be implemented within the language. For example you can have:

if (condition && otherCondition)

changed to

if (condition)
{
    if (otherCondition)
    {

which makes sense, because at run time you'll know the value of the first argument and based on that can determine whether you need to determine the second at all. Having to write those if-statements in all scenarios would become a little tiring though!

The evaluation process you describe is vastly different however. You basically want to pass the argument, which is the result of a function, and only afterwards once you have entered the function and determined you don't need that value that you asked C# to compute for you upon entering, decided to not compute it after all. More importantly, what if gdc had side-effects such as printing something as console output? You called the function, so you'd at least expect the result to show up at the time of calling it! Such side-effects make something alike near impossible and so is deciding in advance whether a function will have side-effects. However, this isn't a problem for either of the boolean operators.

What you're looking for is called lazy evaluation. There's at least one language I know supporting it, which is Haskell, a functional programming language without side-effects, making things quite a bit easier ;)

For this case, I'd simply try to modify the structure so that the function you're calling instead computes gdc and you pass the arguments you would have passed to gdc. That way, only if the function you call evaluates the arguments, will gdc be computed. Of course storing/caching the results of particular calls to gdc would help too, if performance really is a problem.

Share this post


Link to post
Share on other sites
Now that begs new questions, like how come operators can be "shortcut" like && (i.e. operands are not evaluated before the body of the operator is executed) and methods seemingly can not.

You're using knowledge about the function that in general doesn't exist, or at least, you cannot specify it as a single function.

real f(int a, int b, int c) { .. }

quite literally states "I am function f, I need three integers, and return a real to you".

It doesn't state "I need a, and b when it's in the afternoon, and c if a is more than 5 and it is high tide". The C# language also doesn't provide any means to state these things as part of the function f.

 

If you really want, you can code this of course, by making functions f1..f5, testing the usage conditions outside the function, and then call the correct f_i variation. At that point however, it's not language specification any more, but a thing coded by the programmer (user of C#).

 

The standard math (ie what mathematicians use) "and" operation is not short-circuit. It evaluates both sides, and then computes the overall result, just like any other binary operator, like + or *. This kind of "and" however is very annoying in programming.

 

Imagine you have to check for two conditions:

1) list may not be empty

2) first element must be at least 5.

With math "and", mylist.size() != 0 "and" mylist[0] == 5 fails. For the empty list, "mylist[0] == 5" cannot be computed and it throws an exception OutOfRangeError or so. The reason is that math-and first evaluates both sides, and then computes the overall result.

 

The computer-and && is specifically designed to avoid such problems. mylist.size() != 0 && mylist[0] == 5 works, because the && doesn't even consider the second operand if the first one doesn't hold. You should be able to find this in the evaluation of the && operator.

Edited by Alberth

Share this post


Link to post
Share on other sites

In addition to what others have said, let's say you have a method that looks kinda like this.  waaay over-simplified, but bear with me.

int DoStuff(bool b, int x, int y)
{
   if (b)
     return x;
   return y;
}

Pretty simple, right? Now let's assume that you're calling it like this

int x = SomeReallySlowFunction();
int y = AnotherReallySlowFunction();

int answer = DoStuff(someBoolValue, x, y);

Uggh, we've called both slow functions and we really only need one! Well, we could move the logic out of DoStuff, but let's assume that DoStuff is actually a bit more complicated and we don't want to do that. 

What are our options?

Well, you could put the parameters into a class with lazy evaluated properties, but that's kind of a pain.

But the other option is to pass delegates instead of values, i.e.

int DoStuff(bool b, Func<int> getX, Func<int> getY)
{
   if (b)
      return getX();
   return getY();
}

// called like this
int answer = DoStuff(someBoolValue, SomeReallySlowFunction, AnotherReallySlowFunction); 

Note the lack of () on the two method calls. Now, we are only calling whatever methods we actually need.

That said, passing a whole bunch of parameters into a method that probably won't need them is usually a code smell and an indication that you should refactor your method.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this  

  • Partner Spotlight

  • Forum Statistics

    • Total Topics
      627657
    • Total Posts
      2978472
  • Similar Content

    • By STRATUM the Game
      Hey, everyone! This is my first post here.
      I would like to know what you think about my project called STRATUM. It's a 2D platformer that is heavily based on storytelling and boss fighting while trekking through the world.

      Everything in STRATUM takes place in the first century AD, in a world that wraps our own universe, called  The Stratum. A parallel Universe that is the home of the Christian deities . In this game you will play as a Dacian warrior, unfamiliar with everything in this world, you’ll get to know and understand The Stratum together with him.
      The main thing that I want with STRATUM is to reinvent the known lore and history of the Christian deities and realms. 
      The story is unconventional, it plays down a lot of the mysticism of Hell or Heaven and it gives it a more rational view while keeping the fantastic in it. What do I mean by that? Well, think about Hell. What do you know about it? It's a bad place where bad people go, right? Well, that's not the case in STRATUM. I don't want to describe such a world. In STRATUM, there is a reason for everything, especially for the way Hell is what it is in the game. "Hell" is called The Black Stratum in the game.
      This world is not very different from Earth, but it is governed by different natural laws.
      The story will also involve the reason why this world entered in touch with ours.

       
      What do you think about all that I said? Would you be interested in such a game? I have to say that everything is just a work of fiction made with my imagination. I do not want to offend anyone's beliefs.
      I want this to be a one man game. I have been working alone on it (this was my decision from the beginning) from art to effects to programming to music to sound effects to everything.
      I also have a youtube video HERE if you want to see the way the game moves and the way my music sounds.
      Please, any kind of feedback will be highly appreciated. If you have something bad to say, do it, don't keep it for yourself only. I want to hear anything that you don't like about my project.
       
    • By LimeJuice
      Hi, it's my first post on this forum and I would like to share the game I am working on at the moment.
      Graphics have been made with Blender3D using Cycle as a renderer and I am using Unity3D. It's a 2D game, one touch side-scrolling game for iOS and Android.
      Here some pictures, and you can have a look to the gameplay on this video :
      Feedbacks ?
      And if you want to try it, send me your email and I will add you to the beta tester list!
       
       








    • By Kirill Kot
      An adventure indie game with quests in a beautiful, bright world. Characters with unique traits, goals, and benefits. Active gameplay will appeal to players found of interactivity, especially lovers of quests and investigations.
      Available on:
      Gameroom (just open the web page and relax)
      AppStore
      GooglePlay
      WindowsPhone

    • By Kirill Kot
      Big Quest: Bequest. An adventure indie game with quests in a beautiful, bright world. Characters with unique traits, goals, and benefits.
      Mobile game, now available on Gameroom. Just open the web page and relax.
    • By Kirill Kot
      Big Quest: Bequest. An adventure indie game with quests in a beautiful, bright world. Characters with unique traits, goals, and benefits.
      Mobile game, now available on Gameroom. Just open the web page and relax.
  • Popular Now