0

In a project I am working on we load various entities from the database, do some work then attempt to save the resulting entities.
If saving the result is not successful, we return a Result object containing any errors. If it is successful, we return Success = true & Messages = new string[0] and don't use it ever again in the rest of the method.

public class Result
{
    public bool Success {get; set;}
    public IEnumerable<string> Messages { get; set;}
}

public class Entity
{
    public IEnumerable<Things> Things {get; set;}
    public IEnumerable<MoreThings> MoreThings { get; set;}
    public IEnumerable<EvenMoreThings> YouGetWhereThisIsGoing {get; set;}
}

public async Task<Result> DoSomeWork(...)
{
    Entity x = await myService.LoadAsync(identifierParam).ConfigureAwait(false);

... do some work with x ...
    
    var saveResult = await myRepoService.SaveAsync(x.Things).ConfigureAwait(false);
    if (!saveResult.Success) return saveResult;

    saveResult = await myOtherRepo.SaveAsync(x.MoreThings).ConfigureAwait(false);
    if (!saveResult.Success) return saveResult;

    saveResult = await yetAnotherRepo.SaveAsync(x.YouGetWhereThisIsGoing).ConfigureAwait(false);
    if (!saveResult.Success) return saveResult;

... assuming all goes well ...

    return new Result { Success = true, Messages = new string[0] };
}

This has been extremely simplified, but in this example is reusing the saveResult actually accomplishing anything/more readable than
var thingSaveResult = ...SaveAsync(x.Things)
...
var moreThingsSaveResult = ...SaveAsync(x.MoreThings)
...etc.?

5
  • 4
    No, it's a false economy. From a performance/efficiency standpoint, it's probably a wash; the compiler will almost certainly optimize out the extra variables. And in the context you're using these variables, a little bit of extra memory or processing time isn't going to matter anyway, so clarity is king. Mar 29, 2021 at 15:12
  • Does this answer your question? Is micro-optimisation important when coding?
    – gnat
    Mar 29, 2021 at 15:14
  • 1
    Personally I would be more concerned about saving many things in a single method when apparently any of them can fail, leaving you with a partially updated database.
    – Rik D
    Mar 29, 2021 at 18:47
  • Be careful of your scope when reusing variables but yes, you want to reuse objects as much as humanly possible it has a large impact (consider the overhead of GC compared to overhead of reusing an already allocated one). Apr 22, 2021 at 10:00
  • From the title of the question, I get the sense that you misunderstand variables and object. Each successive call to saveResult = await ... is created a new object. It's the variable that's reused and reassigned. There's no fewer objects being allocated because you used the same saveResult variable several times, instead of using unique names for each method call.
    – Alexander
    Jun 8, 2022 at 19:28

3 Answers 3

7

This is not a correct way to think about performance. As a result, you'll make your code harder to follow and more error prone, while having no gains in performance.

The correct way consists of profiling the piece of code to determine the exact location of the bottleneck. Profiling is an essential and mandatory step: you shouldn't try to guess where is the slow part of the code, as you'll get it wrong most of the time (even if you're an experienced developer). Profiling, on the other hand, will show the exact location of the problem, telling, for instance, that the piece of code is wasting 95% of the time doing ToList of a given sequence.

Once you identified it, you think about possible ways to modify the code in order to remove the bottleneck. You formulate hypotheses, and then check them, using either profiling, or benchmarking. Having actual measures is crucial here: you can't claim you optimized something if you can't prove it by showing the actual data.

When formulating hypotheses about possible optimizations, one of the useful techniques is to go see the IL code produced by the compiler. A side effect is that by looking at the IL, you'll understand more not only about the optimizations performed by the compiler, but also about the language itself. Just for fun, do it in relation to your question. Create two pieces of code. Compile. Disassemble. Look at the IL. Noticed anything interesting?

4
  • 2
    Moreover, it's almost always better to get an application working properly and then consider optimizations (if needed.) You can spend a lot of time optimizing something only to find out it's doing the wrong thing.
    – JimmyJames
    Mar 29, 2021 at 17:34
  • @JimmyJames almost always? What situations warrant "doing the wrong thing (allegedly) fast"?
    – Caleth
    Mar 30, 2021 at 8:28
  • 2
    @Caleth There are always exceptions. The one thing that pops to mind is if the implementation is so slow that you can test it in a reasonable amount of time. You are often better off optimizing the unproven solution than waiting hours to find out if it's right.
    – JimmyJames
    Mar 30, 2021 at 14:17
  • Jimmy, I would say the first step should be to write code that works and is readable. It’s easier to make code correct if it is readable. And readable code tends to be faster because it doesn’t have unneeded complexity. (And if it turns out you need to optimise, that’s easier if you start with readable code).
    – gnasher729
    Jun 6, 2022 at 18:17
0

I'll add one more point because the accepted answer is correct, but overlooking an important aspect of the question. With modern compilers/runtime environments creating a new local variable has very little cost on performance. If you tried to profile the two different codes you woul find very difficult to notice any difference. However an efficient use of the variables in your code helps also to reduce chances of a bug. I'll explain what I mean with an example. If you write:

var moreThingsSaveResult = await myOtherRepo.SaveAsync(x.MoreThings).ConfigureAwait(false);
if (!moreThingsSaveResult.Success) return saveResult;

Then someone tells you that you have to change the method to save moreThings in another DB. You copy two lines and you change three names, two in the first line, one in the second:

var moreThingsOtherDBSaveResult = await myThirdRepo.SaveAsync(x.MoreThings).ConfigureAwait(false);
if (!moreThingsOtherDBSaveResult.Success) return saveResult;

How many chances that you forget to change the name of the variable in the second line? Not many, but it does happen. Forgetting to change the name of a variable after a copy/paste of some lines of code is a major source of bugs. The more variables you have the higher the chances to make a mistake. So, even if it seems just a question about performance, actually it isn't.

-4

The FIRST thing you do is write efficient code. There are some things you can assume:

  1. allocating objects on demand takes more time than creating them initially at the beginning. Weigh creating a bunch of global objects over security and efficiency.

  2. depending on the compiler some statements are more efficient than others. for example in C/C++ using if-then-else is more efficient than using switch-case but often more difficult to read and can contribute to errors in logic for complex statements. In some compilers for() loops are more efficient than while() loops.

  3. C# like Java creates Byte-Code which gets compiled with a JIT(Just in Time) compiler. If you build a UWP application there is a tool to compile the .NET IL into object-code i.e. native machine code.

  4. for time critical operations it's often better to build a class library in C/C++ with possible inline assembly since C# doesn't support inline assembly code.

So the question: Which is more efficient, reusing a variable or instantiating a new variable. The answer depends on how C# creates variables. A memory range/slot in the heap must be found followed by allocation and initialization when creating a new variable. Reusing a variable means reusing existing space and reinitializing that space. It seems like reusing a variable would be more efficient but the problem arises when we don't know how the compiler reuses space. Does it simply assign new characters to existing space or does it reallocate that space before assigning the new characters?

For the most part, declaring a variable on demand is fine. It only remains in the scope it was created and when it goes out of scope it's freed for garbage collection. Modern compilers are pretty good at memory management but there is still a cost imposed how ever small for constantly declaring, freeing, garbage collecting variables. Basically think about memory usage and garbage collection. If you can minimize memory management by declaring variables before a loop and then reusing them in the loop you will improve performance.

2
  • 3
    "The FIRST thing you do is write efficient code." No, the first thing you should do is to write clear code. Jun 6, 2022 at 8:36
  • 4
    None of what you wrote matters in terms of performance when the application accesses a database. Any optimization you make on the grounds you listed is dwarfed by the external communication.
    – nvoigt
    Jun 6, 2022 at 9:13

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.