Search Unity

Question different between "in" and "RefRO" keyword ?

Discussion in 'Entity Component System' started by DatCong, Jun 6, 2023.

  1. DatCong

    DatCong

    Joined:
    Nov 5, 2020
    Posts:
    87
    upload_2023-6-7_2-2-26.png upload_2023-6-7_2-0-49.png
    i am confuse about this, is "RefRO" better than "In" about Performance ?
     
  2. xVergilx

    xVergilx

    Joined:
    Dec 22, 2014
    Posts:
    3,296
    Should be none in theory. Both should codegen into read only variable.
    RefRO is more verbose in a way it shows more intent that variable is modified externally and read only.
     
  3. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,269
    With Burst, they are usually the same. But in some cases, RefRO has extra scaffolding that can trip Burst up from making a good optimization. Outside of Burst, you'd have to profile. There are a lot of speculations floating around.
     
  4. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,761
    I think the primary use case of RefRO is

    Code (CSharp):
    1. foreach(var component in SystemAPI.Query<RefRO<MyComponent>>())
    2. {
    3.  
    4. }
    where you can't use 'in'

    In IJobEntity it probably makes no difference. They'll likely both fall into the same traps of make defensive copies of components.
     
    DatCong likes this.
  5. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,269
    That's maybe true if you aren't using Burst, but then that begs the question, why aren't you using Burst?

    With Burst, defensive copies on "in" parameters never happen. If your intent is to use Burst, you should use "in" because it improves Burst's ability to optimize. Sometimes that doesn't matter because Burst will see the optimization anyway, but for particularly complex code it does make a difference. In one project that is heavily bottlenecked by physics and spatial queries, I changed one argument to use "in" and saw a 20% improvement in frame times.

    I've been seeing people lately ruin their APIs thinking that all "in" parameters are bad. I guess if you expect that the code will be called from non-Burst it makes sense, especially for internal logic. But if you expect code to be Burst-compiled, the Burst team literally made a blog post telling people to seriously consider using "in" parameters because it improves their compiler's ability to optimize.

    Sorry for the rant, but this one has been really bothering me lately.
     
    xVergilx, Ryiah and DatCong like this.
  6. DatCong

    DatCong

    Joined:
    Nov 5, 2020
    Posts:
    87
    this is true !!! i was thinking about this, because in programing there is cost of copying data.So i guess The Burst Complie makes "in" have better performance. thanks.
     
  7. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,761
    I know this was more of a general rant but just be be clear, I'm a fan of `in` & `readonly ref` and use them throughout my libraries.
    I think switching to ref to avoid defensive copies is weird as it doesn't matter most of the time and as you say ruins your API.

    I was just pointing out if you were someone who did care about this with 'in', then RefRO doesn't solve it so there's no benefit of switching your IJobEntity executes.
     
    Arnold_2013 and DreamingImLatios like this.
  8. runner78

    runner78

    Joined:
    Mar 14, 2015
    Posts:
    792
    Wherever possible, I almost always only make components readonly structs, so there are no longer any defense copies (outside burst). If components only have a few fields, that shouldn't be a problem either.
    If I remember correctly, RefRO itself are readonly scripts that contain a pointer to the component, so there is no defense copy here either.
     
  9. vectorized-runner

    vectorized-runner

    Joined:
    Jan 22, 2018
    Posts:
    398
    Isn't the Execute method inlined when the code is compiled with Burst? In that case does passing by value/reference matter? If it matters at what struct size does it start making a difference? If anyone has profiled something similar to this I'd like to know your thoughts.
     
  10. elliotc-unity

    elliotc-unity

    Unity Technologies

    Joined:
    Nov 5, 2015
    Posts:
    230
    The story is somewhat different for IJobEntity Execute's vs. normal functions.

    For normal functions, it's true, in bursted code it's fine, and if it really is always going to be bursted in your use case, then `in` is a party. However, when writing library code (such as for us working on entities), we can't really control whether it's always bursted, since people can just turn off burst (e.g. to help iteration time), or if it's not a burst entry point, they can call it from unbursted code. And then the cost of defensive copies can really explode if the struct is large. So I've taken to recommending `ref` for normal functions if you want no copying to happen, because I was traumatized by writing an extension method for the 1186 byte EntityDataAccess struct and realizing that it was spending all its time copying when it was inevitably called from unbursted code.

    For IJE executes, in is fine either way, because a) the `in` turns into a [ReadOnly] attribute on the corresponding ComponentTypeHandle on the generated IJobChunk struct, and b) on top of that, the arguments to IJE execute are generally component types, which are rarely so big that a defensive copy would be truly upsetting.