When using clang with c++17, the string was stored on stack, not heap. When the size of string exceeded beyond 32 bytes that it was stored on the heap. So those of you getting 0 allocations, just increase the size of your string.
That's not true. You're probably thinking about small string optimization, but that's not applicable in any compiler when strings get to a certain size
Yeah this was never the case though, that they reinvented something, or did something "basic" - it was a design goal laid out over a decade ago. string_view was meant as a pre-cursor to the ideas of a views (same notion as ranges and their "views", non-owning, handles of immutable data) - something which is generally wide adopted concept in any new decent programming languages where lifetimes (something which c++ always focused on) & ownership is more explicit in order to squeeze out performance in a much more ergonomic way. And as above commenter mentioned it also provides an interface, so that any string type can be passed to a function taking a string_view, along with utility functions provided. So a std::string implicitly converts to string_view, a {char, len}, converts, but also, unicode strings convert etc etc. Again, the idea is about thinking about ownership (something which was explicitly a design goal in Rust from the start for instance), not just some rando data type.
Some years ago, I wrote my own string class that is a perfect clone of the functions of std::string, except for one major difference: The string is of static size, instantiated through a template argument. This makes it be allocated without an internal pointer to the heap, so it's good for structs and such that don't need to be serialized before saving to file, and it of course stays on the stack if that's where you allocated it. Because it can't change size, it automatically truncates stuff that goes out of bounds, though. Really good for short strings, basically, such as ID tags. It's tradeoff because you're wasting whatever bytes you're not using of a fixed-size string, though.
Important to note, that while string view doesn't allocate, if you have a lot of string literals and rvalues, using string view will cause binary bloat because they have to be explicitly stored in the .data portion of the binary. Just a tradeoff to be aware of
The description of the problem is correct at a high level, and the advice to use string_view is terrific. But the demonstration of the problem is flawed. The first clue is that the allocation size was only 8 bytes, but you'd need at least 14 bytes to store the string. The allocations detected by overriding operator new are not the string buffers themselves. The compiler was set for 32-bit x86 code, so 8 bytes is likely two pointers or a pointer and a size_t. Furthermore, if you try the same experiment in a Release build, there are zero allocations. I believe the 8-byte allocations seen are actually to store extra data to assist with debugging, and are a feature of Microsoft's implementation of the standard library. The library uses the small-string optimization, and apparently the strings in this example are all small, since the buffers are inside the string objects (on the stack) instead in a buffer on the heap. In real code, when you're dealing with strings that a little longer, there will be heap allocations to store the character data, so the advice is still good.
It's not entirely true that std::string is always using the heap allocations. Why no mention about the "Small String Optimization" (SSO) in the std::string?
Liking the focus on optimisation. Keep it coming. Not sure if you cover it elsewhere but it would be great if you could cover optimisation of matrix operations. E.g. rotations of a 3 vect , with same focus on making it faster (but readable).
Should mention that the danger of using std::string_view. You should never take a copy of std::string_view. Using it for a function parameter or a local variable is normally fine. But use extreme caution if you're using it as a data member or returning it from a function. Doing so means there is a good chance the memory std::string_view points to could become invalidated.
This video largely reminded me of other problems with string_view. A common problem with it is, if you need std::string from it, or if there is a chain of strings it becomes a hassle for the compiler to properly optimize it. Such as SSO, string as hex etc... TBH having a simple CoW implementation for const refs is the only use case. But it's definitely a good use case. (Yes I usually use my own CoW implementations..) :P
5:35 I think it will allocate memory because we sent a const char* so it has to make implicit convertion and it will actually allocate space for that string I'm just learning, don't scream at me if I am wrong =(
Please do some videos on STL and their components - iterators, containers, functions and algorithms! And maybe a whole series on algorithms and data structures! That'd be fantastic to learn from you!
You can also write a custom allocator that will allocate memory on the stack for your length-limited string. That would be `using StackString = std::basic_string;` And you also should mention, that you CAN use string_view or const char, but you definetely would be fine with std::string.
instead of: const char* name = "Yan Chernikov"; you can do: std::string_view name("Yan Chernikov"); This way you will reduce your allocations back to zero AND you will save on the C++ convention and not use C convention. For firstName and lastName use name.data().
I think strings have been greatly optimized with c++... no matter how large I make this string I get 0 allocations... it does tally allocations and print alloc sizes but only if i use the new command to allocate memory myself. And then only if I print the allocations before the new object goes out of scope... std::string simply isn't allocating any memory on the heap. Maybe it would in larger programs but this example presented here really doesn't work anymore.
Memory alignment is a pretty technical area which could be explotied for faster string operations. It's much faster to read a 32 bit integer and mask out each byte than to read one byte at a time. If you are someone who likes coding down to the metal, you could use inline assembler and use the SIMD/SSE instruction set to read 128 bits at a time. With some clever optimizations (instruction pairing) you can do operations on 256 bits per CPU clock cycle. I've never tried that on strings myself, but I sure will some day soon.
@@jamesmnguyen I think Google is the best way to go. It was a long time ago I wrote any assembler, except some recent SIMD experiments. If the same rules as during the 90's still hold true, it's pretty simple. Just make sure that one instruction doesn't depend on the result of the previous one. The CPU will fetch two such independent instructions at once and execute.
Amazing work, you're just THE mentor of mine. Thaink you for your work and be aware that yor videos made me pass my cs classes during my studies. Thank you so much.
FORTH language had ‘string views’ from the very beginning in the previous century. Actually all ‘strings’ were tuples of pointer+len. I was wondering why, but now 50 years later I see it was very clever!
Cool! I have been writing C/C++ professionally since 1995, and I have never heard of std::string_view. Thank you for that C++17 hint! I must however warn against using the misleading use of spaces as you do: const char* name = "Yan Chernikov"; The "correct" way of writing this is: const char *name = "Yan Chernikov"; because beginners would otherwise get confused if you e.g. declare the following variables: const char* name, lastname; 'name' is a pointer to a const char (as expected), but ‘lastname’ would just be a ‘const char’, which beginners would miss if they think the '*' belongs to the type, and the same goes for references. Of course you know this, but it is just good practice to not confuse beginners with spacing. Anyway, I just wanted to thank you for this hint.
Nice comment! It's a bit of a holy war which side the * should go, but it's generally agreed upon to simply not use the comma notation for declaring variables, and the confusion you mention is one of the reasons why. Personally I think it can be decently argued that const char *name = "Yan Chernikov"; is good C style, but *not* good C++ style, where you might prefer const char* name = "Yan Chernikov"; instead. The reason for this is the difference in how C and C++ handles types. C is weakly typed, and pointers are not their own types. In C, 'name' would be a pointer variable to a value/array of type 'const char'. Thus it makes sense that the * belongs to the variable, and not the type. In C++, which is strongly typed, 'name' would be a variable of type 'const char*'. Thus it makes sense that the * belongs to the type, not the variable. It's a subtle distinction, but it's particularly important for when you interact with C++-only features such as references and templates. All that said, it probably doesn't matter that much :) Some people even prefer putting spaces both sides of the *!
Nice job cherno !! we need daily something out of box like this!! performance is very very useful in time critical apps, please do more videos on these topic to improve performance. Thanks in advance and keep it up!! 😍
@Artem Katerynych it is not about bad or good - strings are in another part of the memory right? unlike other variables strings cannot release this memory for other variables or functions. Or I am wrong (that can happen as well)
I definetly started with the wrong language. C/C++ is so interesting it completely different than Java or etc. If I master it one day I will sure be happy about it. Thanks for the videos!
std::string is not always the best choice. Often COW strings (Copy On Write) are better and don't require extra classes and complexity to avoid the allocations if implemented right. There are a lot of things in the standard library that aren't really ideal for the general case. They try to cover too much ground and you pay a price for that. Another example is std::shared_ptr which is twice the size of a normal pointer. The simple way is just to a base class with a reference count which is smaller and generally faster.
@Marc Dirven I guess it depends what level of abstraction you are comfortable with. Using pointers and functions like strlen, strncpy, snprintf, strcat and so on to manipulate strings are just as "friendly", if not more to me. You also shouldn't get bugs if you know what you're doing.
@Marc Dirven That's the point, and I do use plain C a lot for greater efficiency. I use C++ where OOP makes more sense and I need to make classes. Many great pieces of software are written using this hybrid approach.
I've got everyone tapped out when it comes to string processing. Script2 uses ASCII Data that grows from stack to heap. I wrote the world's fastest number printing algorithm, which is mainly for integers but it also optimizes pretty much every other FtoS like Grisu and Ryu. Script2 features the Uniprinter (Universal Printer), which can print to any stream source. Unlike the C++ std library, the Uniprinter handles char32_t or char16_t as a sain person would.
im surprised a comment with a claim such as that has gone unoticed for over 2 years damn. So uhm, i did a bit of research regarding what you said about you inventing the world's fastest number printing algorithm, i did find it a bit odd that there's barely anything about you out there. I mean, i've see some stuff like the puff algorithm and the Kabuki Starship thing but when i search for those things barely any results come up, and when i asked some peers they've never heard about anything about this. Im just wondering if you could please go a little bit more in depth about the algorithm you mentioned and explain some specifics.
I guess if it's the app is loaded with string operations you'd want less allocations, but then again the std::string is optimized and compilers can always do this cleaning on their own.
... tbh I think a lot of people are starting to wake up and realize it's not as simple as "more abstraction is better". sometimes it is. sometimes it isn't. oop in general has been a terrible idea that just won't die. I mean, I get why they went that route. In C for example in order to write clean code what inevitably happens is people start putting function pointers in structs with their data (methods). They realize still ppl are mucking about with data members of that struct that are really only intended for "internal use" so they want data encapsulation. They use translation units, static functions etc to emulate "private members". But, it's a bit clunky... OOP was made up to basically streamline this.... but honestly.... they took it too far... inheritence and all that garbage just tightly couples everything together too much and really hurts the promises of "re usability" etc...
Nexus Clarum Well it’s always a matter of knowing what to do and what not to do. You can write a lot messier code in non oop than in oop if you don’t know what you are doing
@@nexusclarum8000 IMO abstraction is great. It allows you to not think about the lower levels, so you don't get distracted. Another thing is that abstraction has to be done right - methods that make sense and are a bottleneck only when you expect them to be, encourage the right style of code. Of course, when the project is old, you inevitably need to change something about those lower levels, so you either have to give up these rules, or change the interface, which will probably lead to a horrible mess and tons of bugs... Maybe I just like OOP because my first language was Java, idk
@@creature_of_fur Abstraction is context dependent. Meaning, you can't abstract code in every project, in every library, in every file. A lot of applications require low level, not abstracted code specifically for clarity sake. "Abstraction is great" means nothing in real world, where not everything is always trivial and most answers start with "Well, it depends..."
Nope. You have to pay for const char*s somewhere. First, initialized const char*s are just immutable string literals. The string literal has to be stored somewhere. Most likely, it will stored in the data block of your binary. Also, memory allocation can be abstracted further by using allocator classes as in std::basic_string. It's worth noting that malloc implementations are really smart today and likely are using arena style allocation to avoid frequently extending the necessary amount of memory needed. If it feels like this is getting complicated, it's because it is complicated. C++ gives the user capability to care as little or as much about performance as they'd like.
tbh, the C++ standard library does tend to overthink things and then completely miss the mark on trying to be bare metal optimized, I love the STL in a way but only because it has functionality, not because its actually that efficient.
Hey, very cool video. I wonder about what is the best way to convert strings into other types (float, int, double). I know there are different ways to approach that but its hard to tell which is most efficent. Would love If you can speak about this topic in one video.
not strings, but i doubled the performance of my games renderer just by switching from a forward renderer to a deferred renderer. like it's now efficient to the point where i can run it on my laptop, which has an IGPU, and it'll actually run well while still looking really good.
I built a code editor and for some reason I uses std::string for the lines of texts. As the text on my code editor grows, the slower it gets. Thanks for this vid.
Thanks for the nice video explaining. I was just refactoring some basic, simple code from std::string to std::string_view and was disappointed to find converting to an int with e.g. std::stoi is not supported and there's apparently no good alternative (std::from_chars would need a custom made wrapper).
On the one end of the spectrum, we have people promoting going back to functional programming for maximum efficiency, and on the other end of the spectrum, we have gigabyte XML transfers for machine to machine communication where it's idiotic to use ASCII. I'm glad my degrees are EE and not schizophrenic CS.
When you overload operator new to call malloc, you should also overload operator delete to call free! Otherwise you write invalid code with undefined behavior!
I came here from the video "small string" and thinking "wait none of these should make the heap allocation, what's going on?" Question: let's assume you have multiple names and don't know them. Would this be possible to do using the space between the names as the divider or is that operation one of those costly things you want to avoid and there's no getting around that problem?
What about SBO cherno. I guess small strings are typically allocated on the stack itself rather than heap so as to save the cost of memory allocation and in your case (9 bytes ) SBO should have got in automatically. Why isn't that happening. Plz correct me if I am wrong somewhere.
@@kristupasantanavicius9093 There is no such thing as a "non-nullable reference" in C++. References can be null just as well as pointers can, because they can be created from pointers and are in fact just pointers with different syntax.
@@oracleoftroy The caller is doing something undefined, since he is performing a dereference. The function being called, which contains reference parameters, is completely valid code. I haven't encountered many cases, if any, where a reference points to a nullptr. Its probably because you have to derefence a pointer when converting it to a reference, which stops you for a moment to think about the possible state of the pointer.
What about optimizing the concatenations of strings? Each concatenation done allocates on the heap 2 times. How to do an easy (and safe) concatenation of two char*?
Help! My code won't build/run in 'Release' vs. 'Debug' mode. In 'Release' it says there is no string_view in std. I am using VS2019 and setting C++ to 17. The answer I saw on MS developer's page didn't seem to help. It was about string_view in general - not specific to 'Release' vs. 'Debug'. Even Chat GPT didn't provide an answer! :)
Why was it only allocating 8 bytes for the string? I would expect something like one byte per character, but you had 13 characters being allocated in 8 bytes.
Great question. I think his string is still 14+ bytes in size (including the null char) and with some additional stuff present in std::string. A good way to test all this is to put a breakpoint inside the new operator overload and verify every allocation manually
I might be wrong about this, but I think it was only 8 bytes because the actual string "Yan Chernikov" is static, so there was no need to allocate it, the actual 8 bytes that *are* being allocated are for the actual string class and it's members/methods
The real answer is that it was't allocating space for the string, but for container proxy (Visual Studio uses it in debug mode for debugging purposes). The storage for the string was't allocated because of SSO (small string optimization). Basically if string is small (in Visual Studio small string is up to 16 bytes), it is stored in a char array which is just a member variable of std::string. This makes constructing and copying small strings faster but increases memory usage of std::string (If string is larger than 16 bytes, this char array remains unused (It's not really wasted entirely, but if you want to go into details just google it)).
*Appreciate you sharing these small bits of code which do enhance the code little by little which is generally neglected or we don't think of. Thanks mate.* ✌️
Guess l have a string like "hello_1_world". And each iteration l want to increase "1" part here. 2, 3 etc... So l can not use const char* pointer or string_view. l have to allocate new memory in every iteration by using std::string. What do you think about it?
It caught my attention that the allocation for the 14 char null-terminated C-string of your name was 8 which I normally presume to be bytes, but uh, that shouldn’t fit?
I don't understand, I copied the first code and compiled it with both -std=c++14 flag and later -std=c++17 flag. None of them printed any byte allocation or any number of allocations.
@dvorak il Although it's convenient with overloaded functions, stronger type safety, and occasionally overloaded operators and template programming. References can be nice as well. But other than that, C is the way to go!
When using clang with c++17, the string was stored on stack, not heap. When the size of string exceeded beyond 32 bytes that it was stored on the heap. So those of you getting 0 allocations, just increase the size of your string.
Gosh this was soo useful, I spent hours trying to figure out why I cannot override the new operator
it's actually when it is longer than 15 characters. Which is not the same as 32 bytes =)
Its look like to small string optimization
That's not true. You're probably thinking about small string optimization, but that's not applicable in any compiler when strings get to a certain size
@@michaplucinski142 actually longer than 16 characters. Well at least on GNU gcc on Ubuntu
I'm glad this series got a sponser. The stuff I've learnt have been very helpful.
Breaking News: C++17 reinvents struct {char*, size_t}
Jakub Rozek better buy these new books
@Peterolen thx! i see both sides of the picture now ^_^
No, it just offers a convenient standard function for people to use. struct{ char*, size_t} was always possible.
Yeah this was never the case though, that they reinvented something, or did something "basic" - it was a design goal laid out over a decade ago. string_view was meant as a pre-cursor to the ideas of a views (same notion as ranges and their "views", non-owning, handles of immutable data) - something which is generally wide adopted concept in any new decent programming languages where lifetimes (something which c++ always focused on) & ownership is more explicit in order to squeeze out performance in a much more ergonomic way. And as above commenter mentioned it also provides an interface, so that any string type can be passed to a function taking a string_view, along with utility functions provided. So a std::string implicitly converts to string_view, a {char, len}, converts, but also, unicode strings convert etc etc. Again, the idea is about thinking about ownership (something which was explicitly a design goal in Rust from the start for instance), not just some rando data type.
I don't get it. People say that manipulating char pointer = undefined behaviour. So what is the purpose of storing string literal in char pointer?
Some years ago, I wrote my own string class that is a perfect clone of the functions of std::string, except for one major difference: The string is of static size, instantiated through a template argument. This makes it be allocated without an internal pointer to the heap, so it's good for structs and such that don't need to be serialized before saving to file, and it of course stays on the stack if that's where you allocated it.
Because it can't change size, it automatically truncates stuff that goes out of bounds, though. Really good for short strings, basically, such as ID tags.
It's tradeoff because you're wasting whatever bytes you're not using of a fixed-size string, though.
Important to note, that while string view doesn't allocate, if you have a lot of string literals and rvalues, using string view will cause binary bloat because they have to be explicitly stored in the .data portion of the binary. Just a tradeoff to be aware of
The description of the problem is correct at a high level, and the advice to use string_view is terrific. But the demonstration of the problem is flawed. The first clue is that the allocation size was only 8 bytes, but you'd need at least 14 bytes to store the string. The allocations detected by overriding operator new are not the string buffers themselves. The compiler was set for 32-bit x86 code, so 8 bytes is likely two pointers or a pointer and a size_t. Furthermore, if you try the same experiment in a Release build, there are zero allocations. I believe the 8-byte allocations seen are actually to store extra data to assist with debugging, and are a feature of Microsoft's implementation of the standard library. The library uses the small-string optimization, and apparently the strings in this example are all small, since the buffers are inside the string objects (on the stack) instead in a buffer on the heap. In real code, when you're dealing with strings that a little longer, there will be heap allocations to store the character data, so the advice is still good.
You can also do this:
using namespace std::literals;
auto s = “Hello“sv;
s will be a string_view
It's not entirely true that std::string is always using the heap allocations. Why no mention about the "Small String Optimization" (SSO) in the std::string?
Maybe if changing from Debug to Release mode?
Thank you, this is a very good point.
GGodis. What he was using was a small string was it not? Even with the SSO it was not allocating those 8 bytes for the characters themselves.
I guess he listened... he just made the video.
0xC0LD yep, he knows that the optimizations usually opts for stack alloc but he purposely used debug mode for heap allocation demonstration.
Liking the focus on optimisation. Keep it coming. Not sure if you cover it elsewhere but it would be great if you could cover optimisation of matrix operations. E.g. rotations of a 3 vect , with same focus on making it faster (but readable).
Should mention that the danger of using std::string_view. You should never take a copy of std::string_view. Using it for a function parameter or a local variable is normally fine. But use extreme caution if you're using it as a data member or returning it from a function. Doing so means there is a good chance the memory std::string_view points to could become invalidated.
This video largely reminded me of other problems with string_view.
A common problem with it is, if you need std::string from it, or if there is a chain of strings it becomes a hassle for the compiler to properly optimize it.
Such as SSO, string as hex etc...
TBH having a simple CoW implementation for const refs is the only use case. But it's definitely a good use case. (Yes I usually use my own CoW implementations..) :P
@@SteinCodes don’t CoW implementations have issues with multi threading scalability ?
Absolutely love these performance optimization videos! Keep up this great work. :)
Working properly with memory is very important for performance. Please share more tips on that. Thank you so much!
5:35
I think it will allocate memory
because we sent a const char*
so it has to make implicit convertion
and it will actually allocate space for that string
I'm just learning, don't scream at me if I am wrong =(
What I learnt :
1. Way to track amount of memory allocation by overloading new operator
2. c_str vs strings and SSO
Since I am working on my own little 2D engine, optimization is the area that interests me the most so I'm on board with more of these types of videos.
Please do some videos on STL and their components - iterators, containers, functions and algorithms!
And maybe a whole series on algorithms and data structures! That'd be fantastic to learn from you!
You can also write a custom allocator that will allocate memory on the stack for your length-limited string. That would be `using StackString = std::basic_string;` And you also should mention, that you CAN use string_view or const char, but you definetely would be fine with std::string.
instead of:
const char* name = "Yan Chernikov";
you can do:
std::string_view name("Yan Chernikov");
This way you will reduce your allocations back to zero AND you will save on the C++ convention and not use C convention. For firstName and lastName use name.data().
Fascinating digging behind the curtain and passion for efficiency gfx programmer core, thank you very much
I love talking about memories and performance with a friend
Wow, and I wondered why my obj file loader was so inefficient with larger models and about 6 strings per line😇 Good Video👍
I think the videos dealing with optimization really help,
and there is not much information on this topic on the internet.
I think strings have been greatly optimized with c++... no matter how large I make this string I get 0 allocations... it does tally allocations and print alloc sizes but only if i use the new command to allocate memory myself. And then only if I print the allocations before the new object goes out of scope... std::string simply isn't allocating any memory on the heap. Maybe it would in larger programs but this example presented here really doesn't work anymore.
Memory alignment is a pretty technical area which could be explotied for faster string operations. It's much faster to read a 32 bit integer and mask out each byte than to read one byte at a time. If you are someone who likes coding down to the metal, you could use inline assembler and use the SIMD/SSE instruction set to read 128 bits at a time. With some clever optimizations (instruction pairing) you can do operations on 256 bits per CPU clock cycle. I've never tried that on strings myself, but I sure will some day soon.
Any resources on instruction pairing? Or just Google it?
@@jamesmnguyen I think Google is the best way to go. It was a long time ago I wrote any assembler, except some recent SIMD experiments. If the same rules as during the 90's still hold true, it's pretty simple. Just make sure that one instruction doesn't depend on the result of the previous one. The CPU will fetch two such independent instructions at once and execute.
It does look like you are just going back to C.
Amazing work, you're just THE mentor of mine. Thaink you for your work and be aware that yor videos made me pass my cs classes during my studies. Thank you so much.
I like your presentation of these kind of problems and making C++ programs run faster. Keep up like this. You are really helping me.
the beat at the intro is just great
FORTH language had ‘string views’ from the very beginning in the previous century. Actually all ‘strings’ were tuples of pointer+len. I was wondering why, but now 50 years later I see it was very clever!
Cool! I have been writing C/C++ professionally since 1995, and I have never heard of std::string_view. Thank you for that C++17 hint! I must however warn against using the misleading use of spaces as you do:
const char* name = "Yan Chernikov";
The "correct" way of writing this is:
const char *name = "Yan Chernikov";
because beginners would otherwise get confused if you e.g. declare the following variables:
const char* name, lastname;
'name' is a pointer to a const char (as expected), but ‘lastname’ would just be a ‘const char’, which beginners would miss if they think the '*' belongs to the type, and the same goes for references. Of course you know this, but it is just good practice to not confuse beginners with spacing. Anyway, I just wanted to thank you for this hint.
Nice comment! It's a bit of a holy war which side the * should go, but it's generally agreed upon to simply not use the comma notation for declaring variables, and the confusion you mention is one of the reasons why.
Personally I think it can be decently argued that
const char *name = "Yan Chernikov";
is good C style, but *not* good C++ style, where you might prefer
const char* name = "Yan Chernikov";
instead.
The reason for this is the difference in how C and C++ handles types. C is weakly typed, and pointers are not their own types. In C, 'name' would be a pointer variable to a value/array of type 'const char'. Thus it makes sense that the * belongs to the variable, and not the type. In C++, which is strongly typed, 'name' would be a variable of type 'const char*'. Thus it makes sense that the * belongs to the type, not the variable. It's a subtle distinction, but it's particularly important for when you interact with C++-only features such as references and templates.
All that said, it probably doesn't matter that much :) Some people even prefer putting spaces both sides of the *!
Nice job cherno !! we need daily something out of box like this!! performance is very very useful in time critical apps, please do more videos on these topic to improve performance. Thanks in advance and keep it up!! 😍
Thank you for making these videos.
I always knew using strings is bad, now I know exactly why - perfect!
@Artem Katerynych it is not about bad or good - strings are in another part of the memory right? unlike other variables strings cannot release this memory for other variables or functions. Or I am wrong (that can happen as well)
@Artem Katerynych yes, I use C/CPP only for ESP32 or other small MCU where every byte counts ;-)
I definetly started with the wrong language. C/C++ is so interesting it completely different than Java or etc. If I master it one day I will sure be happy about it. Thanks for the videos!
"C/C++" isn't a language, it's two languages, with a forward slash sandwiched between them.
Lmao watching this like "Just use C brah"
The very moment I saw the title I knew this would involve C.
I'm going to improve my pluses and English with you
Thanks from Ukraine
this is like catnip for my sense of perfectionism
std::string is not always the best choice. Often COW strings (Copy On Write) are better and don't require extra classes and complexity to avoid the allocations if implemented right. There are a lot of things in the standard library that aren't really ideal for the general case. They try to cover too much ground and you pay a price for that. Another example is std::shared_ptr which is twice the size of a normal pointer. The simple way is just to a base class with a reference count which is smaller and generally faster.
So to make C++ faster we should just use C? hmmm
@Marc Dirven You can modify the string if it's not const. :) So, yes.
@Marc Dirven No, I'd just use a char *, which is a C "string" (or to be more precise, a pointer to a char array).
@Marc Dirven I guess it depends what level of abstraction you are comfortable with. Using pointers and functions like strlen, strncpy, snprintf, strcat and so on to manipulate strings are just as "friendly", if not more to me. You also shouldn't get bugs if you know what you're doing.
@Marc Dirven That's the point, and I do use plain C a lot for greater efficiency. I use C++ where OOP makes more sense and I need to make classes. Many great pieces of software are written using this hybrid approach.
My favorite is when Java/Java Script/C# devs fight against the garbage collector.
I love these optimization videos.
I've got everyone tapped out when it comes to string processing. Script2 uses ASCII Data that grows from stack to heap. I wrote the world's fastest number printing algorithm, which is mainly for integers but it also optimizes pretty much every other FtoS like Grisu and Ryu. Script2 features the Uniprinter (Universal Printer), which can print to any stream source. Unlike the C++ std library, the Uniprinter handles char32_t or char16_t as a sain person would.
im surprised a comment with a claim such as that has gone unoticed for over 2 years damn. So uhm, i did a bit of research regarding what you said about you inventing the world's fastest number printing algorithm, i did find it a bit odd that there's barely anything about you out there. I mean, i've see some stuff like the puff algorithm and the Kabuki Starship thing but when i search for those things barely any results come up, and when i asked some peers they've never heard about anything about this. Im just wondering if you could please go a little bit more in depth about the algorithm you mentioned and explain some specifics.
Thank you so much. That's exactly what i wanted to know. I appreciate that!
I guess if it's the app is loaded with string operations you'd want less allocations, but then again the std::string is optimized and compilers can always do this cleaning on their own.
Video for performance while comparing strings 😊
Moral of the story: never use std::string for performance-critical tasks like games
Yes! Another C++ video. It really sets characters
void print(const std::string& s) {
std::cout
It might not allocate memory because the string is less than 15 characters. But if it was longer, I think it would allocate.
3 allocations.
[Answer] I think it would allocate 5:40
Hey Cherno,can you make a video for *sockets* ?They are a little bit complicated for me.Thank you. :)
Different result, Linux here, the operator new is not called unless firstName and lastName are long enough(>15), C++17 + Linux here.
Is there a name for that thing you do with your arms at the begging of each video?
Memory Mapped files would be good.
So the TL;DR of this video is just: Use const char instead of std::string just like good old c
... tbh I think a lot of people are starting to wake up and realize it's not as simple as "more abstraction is better". sometimes it is. sometimes it isn't. oop in general has been a terrible idea that just won't die. I mean, I get why they went that route. In C for example in order to write clean code what inevitably happens is people start putting function pointers in structs with their data (methods). They realize still ppl are mucking about with data members of that struct that are really only intended for "internal use" so they want data encapsulation. They use translation units, static functions etc to emulate "private members". But, it's a bit clunky... OOP was made up to basically streamline this.... but honestly.... they took it too far... inheritence and all that garbage just tightly couples everything together too much and really hurts the promises of "re usability" etc...
Nexus Clarum Well it’s always a matter of knowing what to do and what not to do. You can write a lot messier code in non oop than in oop if you don’t know what you are doing
@@nexusclarum8000 IMO abstraction is great. It allows you to not think about the lower levels, so you don't get distracted.
Another thing is that abstraction has to be done right - methods that make sense and are a bottleneck only when you expect them to be, encourage the right style of code.
Of course, when the project is old, you inevitably need to change something about those lower levels, so you either have to give up these rules, or change the interface, which will probably lead to a horrible mess and tons of bugs...
Maybe I just like OOP because my first language was Java, idk
@@creature_of_fur Abstraction is context dependent. Meaning, you can't abstract code in every project, in every library, in every file. A lot of applications require low level, not abstracted code specifically for clarity sake.
"Abstraction is great" means nothing in real world, where not everything is always trivial and most answers start with "Well, it depends..."
Nope. You have to pay for const char*s somewhere. First, initialized const char*s are just immutable string literals. The string literal has to be stored somewhere. Most likely, it will stored in the data block of your binary. Also, memory allocation can be abstracted further by using allocator classes as in std::basic_string. It's worth noting that malloc implementations are really smart today and likely are using arena style allocation to avoid frequently extending the necessary amount of memory needed. If it feels like this is getting complicated, it's because it is complicated. C++ gives the user capability to care as little or as much about performance as they'd like.
Thanks so much! Today im gonna benchmark my engine :)
tbh, the C++ standard library does tend to overthink things and then completely miss the mark on trying to be bare metal optimized, I love the STL in a way but only because it has functionality, not because its actually that efficient.
Hey, very cool video. I wonder about what is the best way to convert strings into other types (float, int, double). I know there are different ways to approach that but its hard to tell which is most efficent. Would love If you can speak about this topic in one video.
@Peterolen If you know the answer then answer. If you dont know then dont answer.
not strings, but i doubled the performance of my games renderer just by switching from a forward renderer to a deferred renderer. like it's now efficient to the point where i can run it on my laptop, which has an IGPU, and it'll actually run well while still looking really good.
I built a code editor and for some reason I uses std::string for the lines of texts. As the text on my code editor grows, the slower it gets. Thanks for this vid.
Thanks for the nice video explaining. I was just refactoring some basic, simple code from std::string to std::string_view and was disappointed to find converting to an int with e.g. std::stoi is not supported and there's apparently no good alternative (std::from_chars would need a custom made wrapper).
With your help a got a string serializer running 10x faster
On the one end of the spectrum, we have people promoting going back to functional programming for maximum efficiency, and on the other end of the spectrum, we have gigabyte XML transfers for machine to machine communication where it's idiotic to use ASCII. I'm glad my degrees are EE and not schizophrenic CS.
When you overload operator new to call malloc, you should also overload operator delete to call free! Otherwise you write invalid code with undefined behavior!
maybe this is the reason why my allocation count is always at 0
I came here from the video "small string" and thinking "wait none of these should make the heap allocation, what's going on?"
Question: let's assume you have multiple names and don't know them. Would this be possible to do using the space between the names as the divider or is that operation one of those costly things you want to avoid and there's no getting around that problem?
So, you mean. Instead of string should we use constant char* ?
What about SBO cherno. I guess small strings are typically allocated on the stack itself rather than heap so as to save the cost of memory allocation and in your case (9 bytes ) SBO should have got in automatically. Why isn't that happening. Plz correct me if I am wrong somewhere.
A Release build would have allowed SSO to kick in and his example program would report 0 allocations.
"yes or no, write a comment below NOW!" xD roflmao
7:10 my first method to get to zero allocations would be using C strings :)
Can you please cover Exceptions and why you don't use them?
They are useless. Search for jon blow's rant on TH-cam to see the reasons why.
@dvorak il The most useful feature in C++ are templates and non nullable references.
@@kristupasantanavicius9093 There is no such thing as a "non-nullable reference" in C++. References can be null just as well as pointers can, because they can be created from pointers and are in fact just pointers with different syntax.
@@notnullnotvoid No, the moment you deference a null pointer, your program is undefined and not considered C++ by the standard.
@@oracleoftroy The caller is doing something undefined, since he is performing a dereference. The function being called, which contains reference parameters, is completely valid code. I haven't encountered many cases, if any, where a reference points to a nullptr. Its probably because you have to derefence a pointer when converting it to a reference, which stops you for a moment to think about the possible state of the pointer.
What about optimizing the concatenations of strings? Each concatenation done allocates on the heap 2 times. How to do an easy (and safe) concatenation of two char*?
With that possible, can you create a video about working with files?
Love the cpp content!
2:22 is a hack that gets you past the intro stuff.
Like !
Video about "String interning" would be helpful !
Help! My code won't build/run in 'Release' vs. 'Debug' mode. In 'Release' it says there is no string_view in std. I am using VS2019 and setting C++ to 17. The answer I saw on MS developer's page didn't seem to help. It was about string_view in general - not specific to 'Release' vs. 'Debug'. Even Chat GPT didn't provide an answer! :)
stack is also in memory, heap is also in memory. in both case cpu need to read memory. so what is the differ ? why stack is faster ?
GPU compute when tho?
i need video of shorcuts you use in visual studio. i know its silly request but dont want to learn from other 😷
Hey, Cheno, which font type you are using in VS, it pretty nice~
more videos about STL pls.
Why was it only allocating 8 bytes for the string? I would expect something like one byte per character, but you had 13 characters being allocated in 8 bytes.
Great question. I think his string is still 14+ bytes in size (including the null char) and with some additional stuff present in std::string. A good way to test all this is to put a breakpoint inside the new operator overload and verify every allocation manually
I might be wrong about this, but I think it was only 8 bytes because the actual string "Yan Chernikov" is static, so there was no need to allocate it, the actual 8 bytes that *are* being allocated are for the actual string class and it's members/methods
@@peribooty That is more plausible
The real answer is that it was't allocating space for the string, but for container proxy (Visual Studio uses it in debug mode for debugging purposes). The storage for the string was't allocated because of SSO (small string optimization). Basically if string is small (in Visual Studio small string is up to 16 bytes), it is stored in a char array which is just a member variable of std::string. This makes constructing and copying small strings faster but increases memory usage of std::string (If string is larger than 16 bytes, this char array remains unused (It's not really wasted entirely, but if you want to go into details just google it)).
@@AdamBucior I tried this program on GCC and Clang on Linux, and I get 17 allocations, each about 40 bytes. Here is the output:
Allocating 40 bytes
Allocating 4 bytes
Allocating 40 bytes
Allocating 4 bytes
Allocating 4 bytes
Allocating 40 bytes
Allocating 40 bytes
Allocating 60 bytes
Allocating 28 bytes
Allocating 40 bytes
Allocating 40 bytes
Allocating 40 bytes
Allocating 40 bytes
Allocating 40 bytes
Allocating 40 bytes
Allocating 40 bytes
Allocating 40 bytes
Hello
Allocations: 17
That's insanely different from Visual Studio.
*Appreciate you sharing these small bits of code which do enhance the code little by little which is generally neglected or we don't think of. Thanks mate.* ✌️
which is your font,so beautiful! like it
There is no reason for me to not like this video. thumb up!
Hey how come you writing code so fast? can you make some video or suggest keys or shortcuts & how it's works?
Yes
Guess l have a string like "hello_1_world". And each iteration l want to increase "1" part here. 2, 3 etc... So l can not use const char* pointer or string_view. l have to allocate new memory in every iteration by using std::string. What do you think about it?
with out const, and not with allocation
char hello[] = "Hello_1_World";
hello[6]++;
for (int i = 0; i < sizeof(hello) - 1; i++)
cout
Thank you
It caught my attention that the allocation for the 14 char null-terminated C-string of your name was 8 which I normally presume to be bytes, but uh, that shouldn’t fit?
yes
I don't understand, I copied the first code and compiled it with both -std=c++14 flag and later -std=c++17 flag. None of them printed any byte allocation or any number of allocations.
It's likely your compiler was able to perform small-string optimization on the example and avoid memory allocations
Main lesson, if you just need a fixed length string, use a c-string.
All this performance talk will surely link back to the Renderer in Hazel...
in macOS this code is not showing any allocation, basically our new overload is not being called
yea same thing for me with g++-9 on OS X
@@luwanV yeah
Same in MSVC if you compile a release build. Yay for small string optimization!
Really enjoy this type of content for sure! Looking forward to more of it!
Gotta love writing C code to optimise C++ :D!
@dvorak il I write both
@dvorak il Although it's convenient with overloaded functions, stronger type safety, and occasionally overloaded operators and template programming. References can be nice as well. But other than that, C is the way to go!
yes pls more of this :D
Yes.
Why not const std::string_view if we're just printing?
I love this video