Sign in to follow this  

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
Posted (edited)
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  

  • Forum Statistics

    • Total Topics
      628730
    • Total Posts
      2984423
  • Similar Content

    • By INTwindwolf
      THE PROJECT

      INT is a 3D Sci-fi RPG with a strong emphasis on story, role playing, and innovative RPG features such as randomized companions. The focus is on the journey through a war-torn world with fast-paced combat against hordes of enemies. The player must accomplish quests like a traditional RPG, complete objectives, and meet lively crew members who will aid in the player's survival. Throughout the game you can side and complete missions through criminal cartels, and the two major combatants, the UCE and ACP, of the Interstellar Civil War.
      Please note that all of our current positions are remote work. You will not be required to travel.
      Talent Needed
       
      Unity Engine Programmer
      Website Administrator
      3D Animator
      We have made great strides in the year 2017! INT has received a comprehensive face-lift compared to the start of the year. We look forward to a productive, fruitful year 2018!
      Revenue-Share
      This is the perfect opportunity to get into the game development industry. Being an Indie team we do not have the creative restrictions often imposed by publishers or other third parties. We are extremely conscientious of our work and continuously uphold a high level of quality throughout our project.
      We are unable to offer wages or per-item payments at this time. However revenue-sharing from crowd-funding is offered to team members who contribute 15-20 hours per week to company projects, as well as maintain constant communication and adhere to deadlines. Currently the crowd-funding campaign is scheduled for the year 2018. Your understanding is dearly appreciated.
       
      Thank you for your time! We look forward to hearing from you!
       
      John Shen
      HR Lead
      Starboard Games LLC
    • By Apollo Cabrera
      Energy particles being harnessed by collection multi-hedron energy matrix. Whuuuttt?
      Love it :)
    • By AndySv
        Total Music Collection (http://u3d.as/Pxo)   THE COLLECTION CONTAINS:   Mega Game Music Collection   Universal Music Collection   Huge library of high quality music for any project! All at an incredibly low price!   - 2,5GB of high quality audio - 100+ different music tracks - Loop and short versions   Action, fantasy, casual, horror, puzzle, epic, dramatic, romantic, positive, inspiring, motivational and more!
    • By Dafu
      FES Retro Game Framework is now available on the Unity Asset Store for your kind consideration!
      FES was born when I set out to start a retro pixel game project. I was looking around for an engine to try next. I tried a number of things, from GameMaker, to Fantasy Consoles, to MonoGame and Godot and then ended up back at Unity. Unity is just unbeatable in it's cross-platform support, and ease of deployment, but it sure as heck gets in the way of proper retro pixel games!
      So I poured over the Unity pipeline and found the lowest levels I could tie into and bring up a new retro game engine inside of Unity, but with a completely different source-code-only, classic game-loop retro blitting and bleeping API. Months of polishing and tweaking later I ended up with FES.
      Some FES features:
      Pixel perfect rendering RGB and Indexed color mode, with palette swapping support Primitive shape rendering, lines, rectangles, ellipses, pixels Multi-layered tilemaps with TMX file support Offscreen rendering Text rendering, with text alignment, overflow settings, and custom pixel font support Clipping Sound and Music APIs Simplified Input handling Wide pixel support (think Atari 2600) Post processing and transition effects, such as scanlines, screen wipes, screen shake, fade, pixelate and more Deploy to all Unity supported platforms I've put in lots of hours into a very detail documentation, you can flip through it here to get an better glimpse at the features and general overview: http://www.pixeltrollgames.com/fes/docs/index.html
      FES is carefully designed and well optimized (see live stress test demo below). Internally it uses batching, it chunks tilemaps, is careful about memory allocations, and tries to be smart about any heavy operations.
      Please have a quick look at the screenshots and live demos below and let me know what you think! I'd love to hear some opinions, feedback and questions!
      I hope I've tickled your retro feels!



      More images at: https://imgur.com/a/LFMAc
      Live demo feature reel: https://simmer.io/@Dafu/fes
      Live blitting stress test: https://simmer.io/@Dafu/fes-drawstress
      Unity Asset Store: https://www.assetstore.unity3d.com/#!/content/102064

      View full story
    • By Dafu
      FES Retro Game Framework is now available on the Unity Asset Store for your kind consideration!
      FES was born when I set out to start a retro pixel game project. I was looking around for an engine to try next. I tried a number of things, from GameMaker, to Fantasy Consoles, to MonoGame and Godot and then ended up back at Unity. Unity is just unbeatable in it's cross-platform support, and ease of deployment, but it sure as heck gets in the way of proper retro pixel games!
      So I poured over the Unity pipeline and found the lowest levels I could tie into and bring up a new retro game engine inside of Unity, but with a completely different source-code-only, classic game-loop retro blitting and bleeping API. Months of polishing and tweaking later I ended up with FES.
      Some FES features:
      Pixel perfect rendering RGB and Indexed color mode, with palette swapping support Primitive shape rendering, lines, rectangles, ellipses, pixels Multi-layered tilemaps with TMX file support Offscreen rendering Text rendering, with text alignment, overflow settings, and custom pixel font support Clipping Sound and Music APIs Simplified Input handling Wide pixel support (think Atari 2600) Post processing and transition effects, such as scanlines, screen wipes, screen shake, fade, pixelate and more Deploy to all Unity supported platforms I've put in lots of hours into a very detail documentation, you can flip through it here to get an better glimpse at the features and general overview: http://www.pixeltrollgames.com/fes/docs/index.html
      FES is carefully designed and well optimized (see live stress test demo below). Internally it uses batching, it chunks tilemaps, is careful about memory allocations, and tries to be smart about any heavy operations.
      Please have a quick look at the screenshots and live demos below and let me know what you think! I'd love to hear some opinions, feedback and questions!
      I hope I've tickled your retro feels!



      More images at: https://imgur.com/a/LFMAc
      Live demo feature reel: https://simmer.io/@Dafu/fes
      Live blitting stress test: https://simmer.io/@Dafu/fes-drawstress
      Unity Asset Store: https://www.assetstore.unity3d.com/#!/content/102064
  • Popular Now