The best way to create a string in C# that you shouldn't use

Sdílet
Vložit
  • čas přidán 4. 06. 2024
  • Get tickets to NDC Oslo here: ndcoslo.com
    Check out other NDC conferences here: ndcconferences.com/
    Subscribe: bit.ly/ChapsasSub
    Become a Patreon and get source code access: / nickchapsas
    This video is sponsored by NDC Conferences
    Hello everybody I'm Nick and in this video I am going to talk about what is the best way to create a string in C# in terms of speed and memory allocation. We don't just focus on creating the string itself, since that is easy enough but we are going to put it in context with a pretty common problem and see how this approach fixes the problem.
    Timestamps
    The problem - 0:00
    The simple solution - 2:13
    First optimization - 5:44
    Second optimization - 7:32
    The best way - 9:23
    Don't forget to comment, like and subscribe :)
    Social Media:
    Follow me on GitHub: bit.ly/ChapsasGitHub
    Follow me on Twitter: bit.ly/ChapsasTwitter
    Connect on LinkedIn: bit.ly/ChapsasLinkedIn
    Keep coding merch: keepcoding.shop
    #csharp #dotnet #span

Komentáře • 172

  • @nickchapsas
    @nickchapsas  Před 2 lety +41

    For those of you wondering about missing methods and how they would perform, I updated the code with more benchmarks and I added their results here: gist.github.com/Elfocrash/14dc6de96917c564c80e88a319effb32 (Options that require unsafe code are disqualified from the benchmarks)

    • @DaFileServer
      @DaFileServer Před 2 lety

      @@Bertiii That's very bad practice, because you are modifying the existing string, ClearValue, which is supposed to be immutable. There are no guarantees that the string instance won't be used again elsewhere in .NET especially in the case where you would perform a hash on that password for storage into a database.

  • @mabakay
    @mabakay Před 2 lety +22

    StringBuilder can be written in a simpler way that will take 40B less.
    var sb = new StringBuilder(ClearValue.Length);
    sb.Append(ClearValue, 0, 3);
    sb.Append('*', ClearValue.Length - 3);

  • @michaelsutherland5848
    @michaelsutherland5848 Před rokem +4

    Nick, I've been a C# dev for almost two decades, and you keep teaching me new things. This time, it's that string has constructors. Thanks!

  • @Almamu
    @Almamu Před 2 lety +82

    I'm surprised that you did not mention another way to solve it, which is the second best in memory and time: ClearValue.Substring(0, 3).PadRight(ClearValue.Length, '*'); and is the first one that came to mind (and IMO the one that you should be using instead of the string.Create approach as it should be the easiest to understand). Nice explanation about string.Create, didn't know I could use it like that tho ;)

    • @nickchapsas
      @nickchapsas  Před 2 lety +19

      I could not mention every possible way, there are probably 10s of ways to solve the problem and I wanted to show some fairly common ones that everyone would have used. PadRight on string builder will have similar performance as the string builder one

    • @matteobarbieri2989
      @matteobarbieri2989 Před 2 lety +4

      Would be first choice also for me

    • @ZeroSleap
      @ZeroSleap Před 2 lety +2

      That approach is awesome to know!I didnt know about PadRight,i like string manipulations in general.ONe error i can see though is you should write Padright(ClearValue.Length-3,'*'); because since your substring is the first three characters the rest of the string is asterisks you should subtract three from the overall original length.

    • @Almamu
      @Almamu Před 2 lety +1

      @@nickchapsas oh, no worries, I do understand that ;) I didn't meant to sound like "bUt yOU DiDnT Do THiS or ThAT" i was just surprised that one of the most straightforward (or at least easy to understand imo) wasn't included instead of one of the others. As you've said there's 10s of ways of solving the same problem, so it's good to look at some of them either way. Keep up the good work with the videos ;)

    • @dsvechnikov
      @dsvechnikov Před 2 lety +3

      @@ZeroSleap actually, no. PadRight takes final string length as parameter, not amount of symbols to be added.

  • @sunindragupta9236
    @sunindragupta9236 Před 2 měsíci

    your example is very simple in terms of what you are trying to achieve, most of the times the alogorithim is much more compliated, I apprecate that we should keep your suggestions in mind

  • @trevorscrews5372
    @trevorscrews5372 Před 2 lety +3

    I immediately had a use case (shockingly) for this and it did have about 40% off my execution times. Thanks for this and all your other great videos!

  • @diego_samano
    @diego_samano Před 2 lety +13

    Ok now I'm scared. I was just working with strings yesterday and now exactly the same approach for string optimization. Great video!

  • @KvapuJanjalia
    @KvapuJanjalia Před rokem +1

    I use string.Create to construct cache keys (whose lengths are known in advance): it happens on a very hot path and is worth doing.

  • @jackkendall6420
    @jackkendall6420 Před 2 lety +18

    Span is really cool. It feels like engaging with memory in a low-level C way with the benefits of type safety

    • @barmetler
      @barmetler Před 2 lety

      Yeah I like it! Span is essentially a non-owning, bounds checked pointer.

  • @rossthemusicandguitarteacher

    String builder is my go-to

  • @mohamedsalman3205
    @mohamedsalman3205 Před 2 lety +1

    Thank you for this. Was searching for an efficient way to fill a string for my batch file serializer

  • @ManjunathPatelappa
    @ManjunathPatelappa Před rokem

    This is insane optimization!......Loved your explanation :)

  • @Aaron31056
    @Aaron31056 Před rokem

    Just want to say that I appriciate your videos :)

  • @muhammadasadhaider6893
    @muhammadasadhaider6893 Před 2 lety +1

    Thanks for the valuable info!

  • @ilh86
    @ilh86 Před 2 lety +20

    I've been really getting in to using Spans where appropriate. I recently rewrote some CSS variable generation code using Spans and a couple of other funky optimisations and it's now a whopping 2000 times quicker and allocates 17 times less. Most of that came from parsing strings for any hex values and then create Color structs from those, that particular bit of code is over 13000 times faster and allocates 0 compared to nearly 10KB before.

    • @ghevisartor6005
      @ghevisartor6005 Před 2 lety +5

      hi, may i ask you if you have this code on a public repository just to check it for learning purposes? Or if you have any resources to share about it. Thanks!

    • @hichaeretaqua
      @hichaeretaqua Před rokem

      @ilh86 Do you have some "getting started with spans" tutorials that you can recommend?

  • @philc8544
    @philc8544 Před 2 lety +1

    Looks like a prime candidate for a string extension method. Great video.

  • @andersonklein3587
    @andersonklein3587 Před 2 lety +2

    The more I learn about Java and C#, the more I fall in love with C++. That low level control, so lovely. How in the world is string an immutable if it's just an array of chars, let me edit it, append to it, do math with the pointers... Give me memory, and let me assign values to it. If I want something to be read-only cache/thread safe, I can do so with mutex, atomic, flags, etc.
    Thanks for sharing this string.Create() it is an amazing tool, although the mask creation logic was also really clever!

    • @koszeggy
      @koszeggy Před 2 lety +2

      You can mutate a string even in C# if you really want to (requires enabling unsafe code):
      string result = new string('*', original.Length);
      fixed (char* mutableString = result)
      mutableString[0] = original[0]; // P***********

    • @raphaelbaier6984
      @raphaelbaier6984 Před 2 lety

      You can't really append to a c++ std::string without reallocation going on in the background either. You could make a point for realloc, but if the space is no longer available that will fail, besides realloc is much more C then C++, which would use new and delete instead. The advantage is that you can modify already allocated values, but I would argue that string.Create is practically the same here.

    • @Kazyek
      @Kazyek Před 2 lety

      @@raphaelbaier6984 It's pretty different. string.Create doesn't allow any mutability either; it just allow you to specify how to fill the initial allocation in a much more flexible way.

  • @BrankoDimitrijevic021
    @BrankoDimitrijevic021 Před 2 lety +4

    Just a word of warning: C# char is UTF-16 code unit, and up to 2 UTF-16 code units can form a single Unicode code point (which is closer, but not quite equal, to the abstract concept of "character"). So, any of these solutions could split a code point in half, and effectively modify how the visible part of the string is rendered on the screen.

    • @nickchapsas
      @nickchapsas  Před 2 lety +1

      This is already mentioned in the comments multiple times. It was originally part of the video but it was cut out because I think I’d out of scope. The focus is on the method not the string/char. The char size applies to all of them so they cost equally and they can get sped up equally

  •  Před 2 lety +1

    very good man, thanks

  • @shingok
    @shingok Před 2 lety +8

    The string.Create approach is equivalent in performance to init a Span via stackalloc with SkipLocalsInit in the method with much, much cleaner code. Nice to have in the toolbelt.

    • @nickchapsas
      @nickchapsas  Před 2 lety +1

      SkipLocalsInit requires unsafe code. It is a big no-no. Also, it is not faster.
      | Method | Mean | Error | StdDev | Gen 0 | Allocated |
      |----------------- |---------:|----------:|----------:|-------:|----------:|
      | StackAllocCreate | 9.503 ns | 0.0781 ns | 0.0730 ns | 0.0029 | 48 B |
      | MaskStringCreate | 7.846 ns | 0.0701 ns | 0.0655 ns | 0.0029 | 48 B |

  • @JVimes
    @JVimes Před rokem

    Changing it to var made me happy 😊

  • @sdramare864
    @sdramare864 Před 2 lety +1

    Span result = stackalloc char[value.Length];
    result.Fill('*');
    for (int i = 0; i < 3; i++)
    {
    result[i] = value[i];
    }
    return new string(result); - more faster than string.Create(no waste time on action call and you don't need to copy all source chars)

    • @nickchapsas
      @nickchapsas  Před 2 lety

      It is not. I am benchmarking it and it is always performing worse with an average of 1 nanosecond slower.

  • @mahesh_rcb
    @mahesh_rcb Před 2 lety +9

    Cool ...
    I use most of the time string concatenation ..and rarely string builder ..
    And never span 😑
    Thanks for sharing this one 👌

  • @radiation70
    @radiation70 Před 2 lety +8

    Love it that u r using jetbrains rider :)

    • @rade6063
      @rade6063 Před 2 lety

      Does it matter tho, Im just asking?

    •  Před 2 lety

      @@rade6063 looks more usable than vs community im using

  • @nagarajm889
    @nagarajm889 Před 2 lety

    Your videos are awesome man! how did you learn all these? what's your learning methodology?

  • @wjc1514
    @wjc1514 Před 5 měsíci

    Very impressive

  • @klekaelly
    @klekaelly Před 2 lety +2

    Would love to see how string interpolation would compare to this. I’ve always heard to use it over concatenation

  • @asdasddas100
    @asdasddas100 Před 2 lety +1

    This is pretty sick

  • @enriquejhc01
    @enriquejhc01 Před 2 lety +4

    Thank for this. I thought stringbuilder was slower that using the string variables. I also liked the benchmark tools used in the video.

    • @Erlisch1337
      @Erlisch1337 Před 2 lety +1

      Why would you think it would be slower?

    • @blackenedsprite8542
      @blackenedsprite8542 Před rokem

      But late to the party, but I think up to 3/4 appends stringbuilder is slightly slower, purely for setting up the string builder in the first place (though the memory is still less). After that it's faster. I wouldn't bother using a sb for two strings for example.
      And for specific things like file paths you have methods to deal with those like Path.Join() etc.

  • @RaterisimoCBA
    @RaterisimoCBA Před 2 lety

    Last optimization was kinda very new syntax and hard to get a grasp on imo. I'll be taking into consideration the new string method and Stringbuilder ones. Cheers !

  • @JennyKaylaCom
    @JennyKaylaCom Před 2 lety +2

    Great video. I would use the StringCreate, but I didn't know about the span method. Question1: What if you did the span "the other way". Fill the entire span initially with "*******" and then "replace" only the first 3 elements from your desired string? I will often do something like this: Create a "**********************" string, arbitrarily large(call it asterisks) and concatinate a substring of asterisks. Question 2: How do those two approaches compare with your 4?

  • @jitterist
    @jitterist Před 2 lety +12

    You did not pass an initial capacity to your StringBuilder. That might have improved its performance even more.

    • @lucassaarcerqueira4088
      @lucassaarcerqueira4088 Před 2 lety

      He actually did

    • @theMagos
      @theMagos Před 2 lety

      Default capacity is 16 so it wouldn't have made much difference (other than allocating 4 less bytes, but it wouldn't have extended the capacity)

    • @R.B.
      @R.B. Před 2 lety +1

      @@lucassaarcerqueira4088 should have used new StringBuilder(ClearValue.Length) to initialize to full required size. When just the first 3 characters were used, it would have to grow the space used when appending. I'd need to double check the constructors that there isn't something which allows you to assign the initial string and the length, but it is the resize which takes place that is in question.

  • @user-pu4qu9my5j
    @user-pu4qu9my5j Před 2 lety

    Thanks for promised video. :) I think it's really useful for your followers because it's fact that not really lot of people know about this new feature.

  • @AnythingGodamnit
    @AnythingGodamnit Před rokem

    Could also Slice that Span before copying, since only the first 3 chars are needed. Haven't measured though.

  • @ZintomV1
    @ZintomV1 Před 2 lety +2

    To improve the StringBuilder approach, specify the estimated number of characters required for the builder, so in this case it would be 12, that way, the StringBuilder doesn't have to keep reallocating new array's each time you Append().

    • @nickchapsas
      @nickchapsas  Před 2 lety +1

      Done that already and replied in a different comment. For this size, it saved 1 ns so it wasn’t noticeable

    • @ZintomV1
      @ZintomV1 Před 2 lety

      @@nickchapsas No probs.

    • @lesnoilesnoi
      @lesnoilesnoi Před rokem

      The default capacity of StringBuilder is 16. So it will not reallocate in this specific scenario. But you can still save 4 bytes by pushing 12 to constructor.

  • @silentdebugger
    @silentdebugger Před rokem

    I'd be curious to see how this compares to stackalloc char[] and then passing that span to the string constructor. Granted that's even more constrained because the max length has to be fixed at compile time, but it seems like it would have comparable performance

  • @petedavis7970
    @petedavis7970 Před 2 lety +1

    Great video. Thanks for mentioning not to optimize if you don't need it. I hate when people write highly optimal, hard to read code in situations where it offers no advantage. My approach is to always try to make the code as readable as possible first. If performance is an issue (rarely), then optimize.
    An author on optimization, back in the 90s, Michael Abrash once wrote that "premature optimization is the root of all evil."

  • @BobFrTube
    @BobFrTube Před 2 lety +3

    filling a span is nice as long as you know you are using 16-bit characters and not emoji characters. Alas, the is a problem with the char type in general. JavaScript buffers are similar to spans and have similar problems. Still, we've come a long way from C# when StringBuilder being the tool of choice.

    • @29Aios
      @29Aios Před 2 lety

      it's not emoji but UTF-8, ie up to 4 bytes.
      You can't create someting like this:
      char x = '😉';
      but you can
      string y = "😉";
      and during debuging there are actually 2 chars in *y* string - 55357 \ud83d and 56841 \uded09 or 0xF09F9889

    • @BobFrTube
      @BobFrTube Před 2 lety

      @@29Aios Alas, even in JavaScript adding the 4-byte characters was a hack. A reminder that you can try to future-proof but you'll fail.

    • @29Aios
      @29Aios Před 2 lety

      @@BobFrTube Well, there is a problem with dynamic size of UTF-8 chars, span suppose to be used as a constant size

  • @GarrethandPipa
    @GarrethandPipa Před 2 lety +1

    30 years ago this wasn't "alot" of memory today it is literally nothing. that is like saying a paper cut is the same as a amputation. String manipulation is a big deal when dealing with HUGH files like when the file is greater than the ram on the PC. Parsing a 3 gig file with 32mg of ram into any possible combination of mailing addresses for insertion into a database that was a real challenge and handling memory and optimized for speed becomes critical for success.

    • @nickchapsas
      @nickchapsas  Před 2 lety

      This isn’t about the amount of memory. It’s about allocating the memory in the first place. What we re trying to prevent is the GC locking our app to collect memory that we can avoid and ultimately that improves stability and speed. The memory itself isn’t really that much of a problem

  • @leonov_am
    @leonov_am Před 2 lety +2

    Greate explanation, but you didn't mention another one method with PadRight, it would be intresting to compare

    • @nickchapsas
      @nickchapsas  Před 2 lety +1

      PadRight is the second best alongside the CharArray approach at 14ns in terms of speed but allocated less memory than char array at 80B compared to 96B

    • @leonov_am
      @leonov_am Před 2 lety

      @@nickchapsas I see, thanks

  • @mikim.6338
    @mikim.6338 Před 2 lety

    In MaskStringBuilder method you can combine first and third line ;)

  • @10199able
    @10199able Před 2 lety +2

    I did not know about string constructors at all

  • @ivancarmenates84
    @ivancarmenates84 Před 2 lety +4

    Insane, what about setting the "count" parameter in the CopyTo command? would it be even 1 micro nanosecond faster? lol

  • @danielmitre
    @danielmitre Před 2 lety

    Is this actually the same of using a char array and a for loop?

  • @theroboman727
    @theroboman727 Před 2 lety

    what about ClearValue[..3].PadRight(ClearValue.Length, '*')?

  • @jjxtra
    @jjxtra Před 2 lety +1

    The last one the copyto is writing every char beyond the 3rd un-necessarily?

    • @nickchapsas
      @nickchapsas  Před 2 lety +1

      In the first iteration yeah but at this length there is no performance hit. I’ve benchmarked it.

    • @jjxtra
      @jjxtra Před 2 lety

      @@nickchapsas Got it. For large strings maybe worth thinking about, but only for the few of us working with large amounts of text :) Thanks for your videos!

  • @_grigoryta
    @_grigoryta Před 2 lety

    The thing that you have to remember about string.Create method is that it uses a delegate. So if you pass some values in it from outside of the delegate scope - it'll take memory for closure allocations

    • @nickchapsas
      @nickchapsas  Před 2 lety

      The whole point is that you should not be passing anything from outside, that's why the length and the initial string are parameters in the delegate in the first place, so they are not captured in closures.

    • @_grigoryta
      @_grigoryta Před 2 lety

      @@nickchapsas Yes, you should not. But you can. If you want to make some computed string and you know the absolute length limit (you can trim the empty space later if it matters) - you might be misled to believe that string.Create would be good for that. I've watched a bit further now and you've actually mentioned closures, so that's on me for my comment eagerness
      I've just stumbled upon a similar task that involves string concatenation and considered all the options (remembering this particular video). I'm actually curious now if closures will make it worse than a regular concatenation

  • @ha1025s
    @ha1025s Před 2 lety

    Can I know what IDE program you are using?

    • @queenstownswords
      @queenstownswords Před 2 lety +2

      The upper left shows a RD - that has to be 'Rider' provided by jetbrains.

  • @nickfilat5553
    @nickfilat5553 Před 2 lety

    What if create char array of the length and fill it with data and return new string(character_array)? Would it be the same as string.Create solution?

  • @lawrencemiller3829
    @lawrencemiller3829 Před 2 lety +1

    I disagree with the use of var because strong data typing helps to reduce issues during development and maintenance. I only use var if forced to by either 3rd party code or those who have authority over the development. I generally use string except when the string is very long, then use string builder. (Captain Obvious can be a good role model:)

    • @nickchapsas
      @nickchapsas  Před 2 lety +4

      var has nothing to do with strong typing. It is type inference. Good naming should make the use of the actual type redundant. If I don’t know what the type is just by looking at the name then I should fix the name not cover up the problem with an explicit type

    • @DickBakerSql
      @DickBakerSql Před 2 lety

      @@nickchapsas I would prefer "StringBuilder stringBuilder = new(blah)" as it clear from the start what the datatype is with no guessing !

  • @jamesdraughn5072
    @jamesdraughn5072 Před 2 lety +3

    I would have just use PadRight, although it's probably not the quickest or best with memory.
    Console.WriteLine(firstChars.PadRight(ClearValue.Length, '*'));

    • @andreipanev
      @andreipanev Před 2 lety +1

      Actually the second best in my benchmarks in terms of speed and memory. uses span internally.

    • @adbirdk
      @adbirdk Před 2 lety +1

      It also reads incredibly well.. Wonder what the performance is like.

  • @julioburgos3962
    @julioburgos3962 Před 2 lety +1

    Would be similar performance using a char array, to the string builder?

    • @nickchapsas
      @nickchapsas  Před 2 lety +2

      It's between new string and string.create. In my tests it runs at 16ns and 96B of allocated memory. (Not with string builder but new string(charArray)

  • @EverRusting
    @EverRusting Před rokem +1

    Couldn't you just generate a span from "Password123" as "Pas" then another as "********" then combine them?

  • @georgehelyar
    @georgehelyar Před 2 lety +1

    You can also use char arrays or unsafe char*s

    • @nickchapsas
      @nickchapsas  Před 2 lety +3

      The chat arrays approach is 16ns. Didn’t add it because it would bloat the video and it’s not as common as the previous ones. Unsafe code was out of the question

    • @georgehelyar
      @georgehelyar Před 2 lety +1

      @@nickchapsas fits the theme of "the best way you shouldn't use" though :D

    • @nickchapsas
      @nickchapsas  Před 2 lety +1

      Haha true true. Might make another video with unsafe stuff as well. I’m always fascinated by how much you can do with unsafe code in C#

    • @moestietabarnak
      @moestietabarnak Před 2 lety

      @@nickchapsas safety has a cost... a huge cost, after you have designed, debugged and al..
      optimizing with unsafe should bring the most performance, and then you add some unit-test for safety

    • @nickchapsas
      @nickchapsas  Před 2 lety

      @@moestietabarnak If I wanted to write unsafe code I'd write C++. This is all within the context of safe code.

  • @morphx666
    @morphx666 Před 2 lety +4

    How about filling the asterisks addressing the string as an array?
    string pwd = ClearValue;
    for(int i = 3; i < pwd.Length; i++) {
    pwd[i] = '*';
    }
    I'm sure this won't be the optimal solution, but I would've like to see it compare to the other methods.

    • @nickchapsas
      @nickchapsas  Před 2 lety +1

      Pretty sure this will create a new string per array allocation. Should be as slow as the first one. It’s definitely not better than string builder and below

    • @morphx666
      @morphx666 Před 2 lety +3

      @@nickchapsas Actually, nevermind... the indexer of a string is actually readonly :facepalm:

    • @morphx666
      @morphx666 Před 2 lety +2

      But with a small change:
      public string StringAsArray() {
      char[] pwd = ClearValue.ToCharArray();
      for(int i = 3; i < pwd.Length; i++) {
      pwd[i] = '*';
      }
      return pwd.ToString();
      }
      We get better memory results than StringBuilder.
      | Method | Mean | Error | StdDev | Gen 0 | Allocated |
      |-------------- |---------:|---------:|---------:|-------:|----------:|
      | StringAsArray | 44.61 ns | 0.935 ns | 1.183 ns | 0.0114 | 48 B |

    • @morphx666
      @morphx666 Před 2 lety

      And for completness sake, here are the results for both StringAsArray and StringAsPointer... pretty interesting they both appear to give the (almost) exact same results.
      public unsafe string StringAsPointer() {
      int len = ClearValue.Length;
      fixed(char* pwd = ClearValue) {
      for(int i = 3; i < len; i++) {
      pwd[i] = '*';
      }
      return new string(pwd, 0, len);
      }
      }
      | Method | Mean | Error | StdDev | Gen 0 | Allocated |
      |---------------- |---------:|---------:|---------:|-------:|----------:|
      | StringAsArray | 44.12 ns | 0.976 ns | 1.335 ns | 0.0114 | 48 B |
      | StringAsPointer | 44.20 ns | 0.452 ns | 0.423 ns | 0.0114 | 48 B |

    • @morphx666
      @morphx666 Před 2 lety +1

      @@alcoholrelated4529 Yes, I should have provided a baseline for comparison.
      Here are the results for the three test-runs:
      | Method | Mean | Error | StdDev | Gen 0 | Allocated |
      |----------------- |---------:|---------:|---------:|-------:|----------:|
      | StringAsArray | 44.26 ns | 0.963 ns | 1.412 ns | 0.0114 | 48 B |
      | StringAsPointer | 39.98 ns | 0.346 ns | 0.324 ns | 0.0114 | 48 B |
      | MaskStringCreate | 98.69 ns | 1.081 ns | 0.958 ns | 0.0440 | 184 B |
      | Method | Mean | Error | StdDev | Gen 0 | Allocated |
      |----------------- |----------:|---------:|---------:|-------:|----------:|
      | StringAsArray | 45.67 ns | 0.842 ns | 0.788 ns | 0.0114 | 48 B |
      | StringAsPointer | 45.79 ns | 1.012 ns | 1.243 ns | 0.0114 | 48 B |
      | MaskStringCreate | 104.77 ns | 2.106 ns | 3.955 ns | 0.0440 | 184 B |
      | Method | Mean | Error | StdDev | Gen 0 | Allocated |
      |----------------- |----------:|---------:|---------:|-------:|----------:|
      | StringAsArray | 44.48 ns | 0.676 ns | 0.600 ns | 0.0114 | 48 B |
      | StringAsPointer | 43.52 ns | 0.584 ns | 0.546 ns | 0.0114 | 48 B |
      | MaskStringCreate | 101.39 ns | 1.448 ns | 1.355 ns | 0.0440 | 184 B |
      I think that StringAsArray is the easiest to implement, and the optimal solution for handling very large strings (not the fastest though).

  • @vothaison91
    @vothaison91 Před rokem

    This will add so many fps to new PC games.

  • @theMagos
    @theMagos Před 2 lety

    Span CopyTo has no offset and length parameters? Feels unneccessary to copy more than 3 characters.

  • @SeanAlunni
    @SeanAlunni Před 2 lety

    Is it faster than using an unsafe block?

    • @nickchapsas
      @nickchapsas  Před 2 lety +1

      No but unsafe code is a no-no in several codebases, so this is the closest you can get without unsafe code

  • @felixp535
    @felixp535 Před 2 lety +2

    In general, I would advise not using strings directly. You might have to localize your program at some point, and I can assure you you don't want to go through your entire program to remove all of the hardcoded strings when that happens. Think ahead, use enums along with some class that returns the correct string for each enum value.
    In my opinion, in the end it's more readable, more futureproof, and it should enforce your team not to concatenate strings everywhere (what works for the english language probably doesn't for other languages like Arabic or Japanese).

    • @tajkris
      @tajkris Před 2 lety

      "localize your program at some point" I disagree. You usually know at the very early stage if you're going to localize in the nearest future (2-3 years, maybe even longer for certain apps), if you don't plan for it then it's time and money wasted on futureproofing. If you do plan it, then you have a lot more to consider than simple "use enum instead of string" - fonts that support diacritics, layout (some text in other language may be waaay longer), right to left text,input methods, icons, graphics or even colors (some symbols are offensive in some regions), even complete ui revamp if habits and culture are vastly different and differencent ui could result in better sales.
      Not saying simple "string replace" localization is bad, but doing it blindly without thinking of scenarios to cover is definitely not a recommended way

  • @Kazyek
    @Kazyek Před 2 lety

    Even faster version:
    [Benchmark]
    public string MaskUnsafe()
    {
    var s = new String('*', Pass.Length);
    unsafe {
    fixed (char* c = s)
    {
    c[0] = Pass[0];
    c[1] = Pass[1];
    c[2] = Pass[2];
    }
    }
    return s;
    }
    (string.Create takes ~14.26ns on my computer; the above takes ~10.86ns, while the above but with a for (var i = 0; i < 3; ++i) loop takes ~12.00ns)

    • @nickchapsas
      @nickchapsas  Před 2 lety

      Like I’ve mentioned in the video and in the pinned comment, unsafe methods are out of the question. If unsafe as allowed then there are even faster approaches than this one but it’s not

  • @Eugene.g
    @Eugene.g Před 2 lety

    NDC is great

  • @DiegoXLT
    @DiegoXLT Před 2 lety

    It looks like it's a tiny bit faster to manualy copy first three chars instead of using AsSpan() - for me it's 20% faster
    public string MaskStringCreateManualCopy()
    {
    return string.Create(ClearValue.Length, ClearValue, (span, value) =>
    {
    for (var i = 0; i < 3; i++)
    span[i] = value[i];
    span[3..].Fill('*');
    });
    }

    • @nickchapsas
      @nickchapsas  Před 2 lety

      The reason why this is faster is because in my example I first copy the full string in the span and then overwrite the last characters

    • @DiegoXLT
      @DiegoXLT Před 2 lety

      I've tested that against already improved version where only 3 chars are copied to the span 'value.AsSpan()[..3].CopyTo(span);' - not sure why doing it manually is faster but it only actually is when copying 3 chars or less... I guess the CopyTo() has better time complexity but with some initial cost.

  • @spinFK
    @spinFK Před 2 lety +1

    so, back to C/C++

    • @digitalconsciousness
      @digitalconsciousness Před 2 lety +1

      Have to admit, being able to just access strings as arrays right off the bat in C and C++ is convenient.

  • @shishlinsv
    @shishlinsv Před 6 měsíci

    One more thing to implement unreplasable developer pattern better😄

  • @nilscoussement
    @nilscoussement Před 11 měsíci

    Am I the only one who would write this?
    [TestMethod]
    public async Task TestString()
    {
    string work = "somestring";
    string encrypted = string.Join(String.Empty, work.ToCharArray()
    .Select((c, index) => index > 2 ? '*' : c)
    );
    }

  • @abdullahaddoun5720
    @abdullahaddoun5720 Před 2 lety

    I’m curious why the 29 people disliked this video!!

  • @krikukiks
    @krikukiks Před 2 lety

    There's no way the compiler doesn't optimize it.
    Obv a easy case for an example but I image quite a common one.

    • @nickchapsas
      @nickchapsas  Před 2 lety +1

      The compiler isn’t that smart. It will do what you tell it and in this case, it won’t.

    • @krikukiks
      @krikukiks Před 2 lety

      ​@@nickchapsas Yeah I guess they thought the benefits are not worth the effort then. Don't have that much knowledge about C# tbh.
      Just assumed it because that's what Java does.

    • @nickchapsas
      @nickchapsas  Před 2 lety

      Java doesn’t have Span or something equivalent and every string “mutation” is a new allocation so in java the 4th approach doesn’t exist. Do you have a scenario where Java will optimise this?

    • @krikukiks
      @krikukiks Před 2 lety

      ​@@nickchapsas
      String numbers = "";
      for(int i = 0; i

  • @plyr2
    @plyr2 Před 2 lety +1

    Disappointed you didn't show unsafe methods, as they do have some use cases within Unity.
    E.g. displaying a millisecond precision level timer onto the screen, unity requires a string for the UI text component, but you can't create a new string every millisecond, or modify one.
    So you overwrite the original string and tell the text component to force--refresh, immutable is just a suggestion in unsafe land.

    • @nickchapsas
      @nickchapsas  Před 2 lety +1

      Unsafe methods were explicitly out of the video's scope

  • @superpcstation
    @superpcstation Před 2 lety

    Before watching the video, my guess is string.Create

  • @prman9984
    @prman9984 Před 2 lety +1

    #3 is the best because it's very easy to read. #4 is not worth the tiny performance boost.

    • @bmazi
      @bmazi Před rokem

      I don't find #4 to be unreadable.

  • @mix5003
    @mix5003 Před 2 lety

    i was thinking that simple solution will optimized by compiler, so i don't need too care that much. but it was not T_T

    • @igorthelight
      @igorthelight Před 2 lety +1

      .NET compiler is not very good at optimizing ;-)
      But the main thing is - programmer must know how to optimize too!
      Otherwise it's just "monkey coding".

    • @moestietabarnak
      @moestietabarnak Před 2 lety

      I never heard of a compiler that optimize a bubble sort into a quicksort..or whatever algo... do they now?
      -signed Old C programmer

    • @mix5003
      @mix5003 Před 2 lety +2

      @@moestietabarnak no it not do that much. if i remember correctly i heard that java will auto convert + operator to string builder, so you can use + without performance penalty. (i known that string builder is better, but for me i think + is more readable)

    • @moestietabarnak
      @moestietabarnak Před 2 lety

      @@mix5003 yup readability trump optimization

  • @hardymasonj
    @hardymasonj Před 2 lety

    "Don't prematurely optimize something just to use a feature."
    I have fallen into this trap so many times. Especially when I first learned Generics. I feel bad for anyone who had to revisit that code later.

    • @nickchapsas
      @nickchapsas  Před 2 lety +2

      Premature optimization quotes are the worst thing that has happened to software engineers.

  • @markmidwest7092
    @markmidwest7092 Před 2 lety

    public string MaskNewStringV2()
    {
    var length = ClearValue.Length - 3;
    return $"{ ClearValue.Substring(0, 3) }{ new String('*', length) }";
    }

  • @JuannesDeBeu
    @JuannesDeBeu Před 2 lety +1

    This could beat them all:
    [Benchmark]
    public string Unsafe () // 50 times faster than Native, no memory
    {
    unsafe
    {
    fixed ( char* p = ClearValue )
    {
    for ( int i = 3 ; i < ClearValue.Length ; i++ )
    {
    *( p + i ) = '*';
    }
    }
    }
    return ClearValue;
    }

    • @nickchapsas
      @nickchapsas  Před 2 lety +1

      The obvious most performant Unsafe approach was one of the things excluded as “acceptable” for this. You’re pinning the string in memory so it’s technically safe but not something that should be used IMO unless the team is heavily using unsafe in other places

  • @OlegKosmakov
    @OlegKosmakov Před 2 lety

    I bet you can save couple more microseconds if you only copy first 3 characters from span in last method.

    • @nickchapsas
      @nickchapsas  Před 2 lety

      Someone tried it in the comments. No difference at all but it’s most likely due to the size of the span. If it was a waaaay bigger one it should be faster

  • @barmetler
    @barmetler Před 2 lety

    hehe (slower than string.create, but also only allocates the final string)
    public unsafe string MaskCharsStack()
    {
    var length = Password.Length;
    var chars = stackalloc char[Password.Length];
    for (var i = 0; i < length; ++i)
    {
    chars[i] = i < 3 ? Password[i] : '*';
    }
    return new string(chars);
    }

  • @user-gk5xh5wd9k
    @user-gk5xh5wd9k Před 2 lety

    выделено 500 укусов :DDDD

  • @sachinkainth9508
    @sachinkainth9508 Před 2 lety

    First

  • @Layarion
    @Layarion Před 2 lety +1

    You know what this tells me? C# lied to us when it said it was trying to make things simpler. Wtf do i have to know all this? why can't i just make a string, and flip on a "optimize for this or that" flag? fuck.

    • @nickchapsas
      @nickchapsas  Před 2 lety +2

      You don't have to know all this and you don't have to optimize any of that. You only have to worry about all that when you've optimized everything else in high level and you now need to do microptimizations

    • @igorthelight
      @igorthelight Před 2 lety +3

      I'm sorryu but you have to know how things works or you will be just "monkey coding" :-)

    • @MikeWardNet
      @MikeWardNet Před 2 lety +1

      Programming is difficult business. It should never be undertaken in ignorance. - Douglas Crockford

    • @igorthelight
      @igorthelight Před 2 lety +3

      @@MikeWardNet True!
      I like C# because it let's you create something useful with very little knowledge. But the more you know - the better your program became.
      Same with Python.

    • @Erlisch1337
      @Erlisch1337 Před 2 lety

      Having to keep track of a lot of flags would not make anything simpler. :)

  • @blazefirer
    @blazefirer Před 2 lety

    everyone this code is way too complex. Me as javascript developer going yeah this is totally how I would do It in javascript

  • @mihailpeykov
    @mihailpeykov Před 2 lety

    The reason I gave you thubs down is because you claim this is the most efficient way, while:
    1. it clearly is not
    2. even if it was, you didn't explore several other approaches, which could be more efficient (and some of them acrually are)
    3. your comment in the comments about heap vs. stack, which shows you don't fully understand what is really going on underneath
    Other people before me already made good points of why this is not the most efficient way and what are the other possible options, so I will not repeat them.
    Apart from that, the goal of this video - to make "the lazy C# developers" think "what happens under the bonet" and in general make them think about performance - I admire that! Just be very careful with claims like "this is the most efficient way" - all you had to do is say "this is a much more efficient way" and you would have been golden! :)

    • @nickchapsas
      @nickchapsas  Před 2 lety

      Firstly, thumbs up or down, they both count as engagement so thank you!
      Now for your points.
      1. Why isn't it? Do you have a better approach that isn't using unsafe code? I'd really like to know
      2. The ones I didn't explore are not faster. I tried A LOT of them and they are all slower than string.Create. The only faster ones use unsafe code, which is out of the question.
      3. Sounds like you are one not understanding heap vs stack. I would really like to know which part is wrong.

    • @mihailpeykov
      @mihailpeykov Před 2 lety

      @@nickchapsas Well, I really don't want to turn this to a public argument, but for the sake of education (including mine, because I could be wrong and actually learn something from this), let me try to explain myself a bit more. Most of these points were already made by
      @Aidiakapi though.
      1. string.Create probably is the fastest way without unsafe code and probably just as fast as with (correct) unsafe code - after all, it is designed to be just that. The reason your code is not the most efficient is because you're copying the whole original string into the new string, instead of only the first 3 characters. I know for this sting length the difference is miniscule, but still faster. For huge strings the difference will be noticeable.
      2. One variant worth exploring is to use StringBuilder with preallocated length (so only 1 memory allocation is done, no reallocating), then copy the first 3 characters with something like stringBuilder.Append(ClearValue, 0, 3), then add the extra '*' characters using stringBuilder.Append('*', length - 3). This is probably the best we could do in the old days (before string.Create and Span) without unsafe code. It is still slower than string.Create because of the additional memory allocation which will happen on stringBuilder.ToString(), otherwise (before the .ToString()) it should be comparable in performance (as it is literally doing the same thing, just not in the final memory).
      3. I am pretty sure all strings reside on the heap. I cannot guarantee (without disassembly) that string.Create doesn't directly allocate heap memory, instead of allocating stack memory and then copying it to a newly allocated heap memory after the delegate is finished, but I don't see why it would - it would only make things worse (less efficient). Imagine you string.Create'd a 100MB string - what would be the point of allocating 100MB of stack memory (which you may not even have), just to copy it later into the heap? And stack memory by itself is not faster than the heap - it is the same kind of memory. Also Span is perfectly capable of "pointing" to heap memory - being ref struct only means that the Span variable itself is always on the stack, not the memory it points to. Think of Span as just a "pointer" and a size, except that "pointer" is a little bit more complicated than a regular unsafe pointer.
      One more thing about unsafe code: using it to override the internal buffer of the input string is a very bad idea, because of shared strings, precomputed hash codes, etc. Using it to change the internal buffer of a newly allocated string is still a hack, but apparently it works (and is the best we could do before string.Create and Span). If you think about it, string.Create is just the better (safer) way of doing the same thing - allocate some memory on the heap and let you fill that memory at the correct time (before the string initialization is finshed, not after that), using Span instead of an unsafe pointer.
      And one more thing worth mentioning - one has to be careful with the delegate passed as callback - if it uses some local variable from the calling function, it becomes a closure, which means heap allocation and all the efficiency goes out the window. Unless the compiler / JIT does some very clever optimisation, which I doubt. This is why the delegate has a 'state' argument passed to it.

  • @JakubSK
    @JakubSK Před 2 lety

    Meh, doesn’t really matter, no need to obsess over minor details. You only live once. Code it and move on. If there’s a problem fix it. It’s only software.

  • @stepkka
    @stepkka Před 2 lety +1

    "Quickly change it to a var".. Instant dislike