Skip to main content
GameDev.net gamedev.net
🔒 Locked

Ch 3 oddity

Started by Sammie22 Jan 22, 2011 at 2:40 AM 5 replies 3.4k views
Original Post
Sammie22
Sammie22
I found this a little strange.. Maybe I'm having a brain fart here but in Managing Memory section, the author states:

"When you pass in a reference type, we are actually passing a copy of the reference to the data."

He then states, in regards to the 'ref' keyword:

"We should not use this keyword on reference types because it will actually slow things down..."

Then, in Program.cs for the PerformanceChecker, he has this static method signature:

static void PrintPerformance(string label, [color="#ff0000"]ref Stopwatch sw) { ... }

At first I was thinking Stopwatch was a struct, but it is a class. So am I missing something here, or did he just say one thing and do another? There is no reason I see to pass as a reference, as it does not redefine the memory pointer for that reference. It only access the member variables of that object.
kiwibonga
kiwibonga
I now have the same question.

I googled around and I couldn't find any other evidence that using ref when passing reference-types would "slow things down..." In fact, NOT using it means the reference is copied before being pushed to the stack, so there's actually a (probably completely negligible) performance hit from doing that copy and then destroying it.

Odd, to say the least.

Stotto
Stotto
Using ILDASM (C:\Program Files\Microsoft SDKs\Windows\v6.0A\Bin\ildasm.exe), you could perform a test to see what the difference is between the two.

Lets say we have a class as follows:

class Foo
{
public int foo1 = 0;
}



And lets say we have two methods:

Foo3 (not using the ref keyword)
static void FooMethod3(Foo foo)
{
foo.foo1 = 3;
}


Foo4 (using the ref keyword)
static void FooMethod4(ref Foo foo)
{
foo.foo1 = 4;
}



So lets take a look at the CIL code using ILDASM to see what is going on behind the scenes:

Foo3 (not using the ref keyword)

.method private hidebysig static void FooMethod3(class RefTest.Foo foo) cil managed
{
// Code size 9 (0x9)
.maxstack 8
IL_0000: nop
IL_0001: ldarg.0
IL_0002: ldc.i4.3
IL_0003: stfld int32 RefTest.Foo::foo1
IL_0008: ret
} // end of method Program::FooMethod3


And Foo4
.method private hidebysig static void FooMethod4(class RefTest.Foo& foo) cil managed
{
// Code size 10 (0xa)
.maxstack 8
IL_0000: nop
IL_0001: ldarg.0
IL_0002: ldind.ref
IL_0003: ldc.i4.4
IL_0004: stfld int32 RefTest.Foo::foo1
IL_0009: ret
} // end of method Program::FooMethod4


Note: if you optimize these two methods you will just get rid of the nop instruction.


So we can see that Foo4 actually has one more instruction than foo3, called ldind.ref. This instruction is us dereferencing the reference we have made. The rest of the instructions are Identical (well, except for us assigning one a value of 3 and the other a value of 4). There is certainly a performance hit due to one extra operation being performed. But there can be arguments for using the ref keyword with reference types as well.

In the case of the example in the book, I feel that your conclusion is spot on Sammie because as you stated, Stopwatch is not a structure, and only instance-members are accessed, with no re-referencing occurring. There currently is no real need for the ref keyword.
Sammie22
Sammie22
Very nice Stotto.. thanks for the reply. And I did not know about that tool, which looks pretty handy.
kiwibonga
kiwibonga
Thanks for the explanation!

Unfortunately, I'm still confused... Why is it that feeding the reference "directly" requires it to be dereferenced, but feeding a copy of it doesn't?

Stotto
Stotto
Kiwi, I think your confusion lies within how the ref keyword actually operates.

Okay, so as we know:

When a value-type is stored, we store the value.
When a reference-type is stored, we store the contents of the type else-where, and in the reference-type, store a pointer to where those contents are located.

Now, when we don't use the ref keyword:

When a value-type is passed in, we pass in a a copy of that value.
When a reference-type is passed in, we pass a copy of the pointer to where the contents are located.

Using the ref keyword:

When a value-type is passed in, we pass in a pointer to where the value is located.
When a reference-type is passed in, we pass in a pointer to where the reference-type is located. That is to say, we pass in a pointer TO the pointer of where the contents are located.


This may seem odd at first, but I will explain why it is done.

Lets say we have two objects, Foo1 and Foo2. Perhaps we want to make a method that will make Foo1 point to Foo2's contents.

So what we want to do is change the pointer in Foo1 to equal the pointer in Foo2. If we only pass in the locations of the contents, but not the locations of the pointers, we wouldn't be able to do this! We couldn't set the pointer in Foo1 to Foo2 because we do not know where the pointer in Foo1 is located.

By passing in a reference to the reference-type, we now now know where the pointers are located. And because of that we also know their contents, though indirectly. We have to follow the first pointer, to the second, which will lead us to the contents. This is why the dereferencing needs to occur when you are accessing a reference-type's members with the ref keyword.

I hope this helped explain that a little better for you. And if you need further explanation feel free to ask.
kiwibonga
kiwibonga
Thanks for taking the time to write all that -- I get it now!

Still learning C# for the workshop...

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.