The Dark Side of .reserve()

Поделиться
HTML-код
  • Опубликовано: 28 сен 2024
  • .reserve(...) is a method you might've seen in the API of your favorite dynamic array (or hash table or whatnot), and it's an excellent tool for making simple, impactful performance optimizations while you are building up data structures. But just like all tools, it has sharp edges. In this video we'll dive into where .reserve() can make your performance sing--and where it can be devastating.
    Special guest appearances from Unreal Engine, spline points, big-O notation, amortized constant complexity, exponential growth, C++ STL algorithms, good Rust design choices, and me forgetting to use the oldSize variable in the call to .resize() on the next line (but it's okay, I use it later on).
    Starring:
    std::vector::reserve - en.cppreferenc...
    Vec::reserve_exact - doc.rust-lang....
    TArray - docs.unrealeng...
    USplineComponent - docs.unrealeng...
    FBVector (great docs on selecting a good growth factor) - github.com/fac...
    Contributing to Unreal - docs.unrealeng...
    I use the amazing Manim library for animating these videos, and I edit them with Blender and Audacity.
    www.manim.comm...
    www.blender.org/
    www.audacityte...

Комментарии • 372

  • @kamilkarwacki9590
    @kamilkarwacki9590 Год назад +40

    I love that you say BOTH C++ and Rust are your favorite programming languages.

  • @ElGnomistico
    @ElGnomistico Год назад +323

    PLEASE keep making videos, I can't get enough of them. You're brilliant!

  • @dprophecyguy
    @dprophecyguy Год назад +16

    I think this whole playlist should be an ideal programming course for any x language. But most of the course end up just reading through the docs and taking us through the syntax of a language.
    Learning a new language is not about learning a syntax and get done with it, its exactly this, these videos which gives the insight of how to desing your programs in rust. These mental models are what that any new comer can learn and can apply to Rust and any other language.
    I am really really thankful to you for doing these sets of videos.

  • @maxwell_edison
    @maxwell_edison Год назад +45

    im a roblox developer and what is this

    • @eineatombombe
      @eineatombombe 5 месяцев назад +4

      you develop roblox or on roblox?

    • @rodrigosouto9502
      @rodrigosouto9502 4 месяца назад +1

      Nice opossum

    • @xijnin
      @xijnin 3 месяца назад +4

      For me, rust is easier than making roblox games lmao

    • @eineatombombe
      @eineatombombe 3 месяца назад +5

      @@xijnin and its not a huge scam designed for tricking children

    • @danser_theplayer01
      @danser_theplayer01 8 дней назад

      I'm a javascript hobbyist I understand just fine what he's saying. In fact I might have thought of similar issues before.

  • @bigpest
    @bigpest Год назад +14

    This rules. Literally hit this issue in a Rust Advent of Code problem where I thought using `reserve_exact` would speed things up - but instead ground to a halt. Cheers!

  • @ShaderKite
    @ShaderKite Год назад +131

    I love the way this video is structured!
    It's so well done that at ~12:00 I stopped the video, looked at the code and thought about it myself. And I arrived at the same conclusion you later gave.
    So basically what a great teacher does: you taught me the basics and with those I was able to figure out the problem myself! :D

    • @henriquekirchheck
      @henriquekirchheck Год назад +1

      Incredible, the realization hit me at the exact same time. Really great video and way of explaining things

    • @ME0WMERE
      @ME0WMERE Год назад +3

      same. He gave the perfect example to make everyone watching the video go 'ohhh' before he showed the conclusion

  • @LetsSmiley
    @LetsSmiley Год назад +7

    8:11 you just made me understand that concept in 2 minutes, what a semesters worth of algorithm teachings could not.

  • @godnyx117
    @godnyx117 Год назад +5

    Great video! Once again, we turn down to "be aware of what you are using and how it works".
    It's interesting to me as I have heard the term optimization been described as "knowing your environment and doing the best job you can to optimize for it" and this is the explanation I choose to use.
    It's funny that a lot of times we try to make optimizations and we don't know our environment very well.

  • @Baptistetriple0
    @Baptistetriple0 Год назад +5

    Very nice video! I just want to add something about why rust vec don't use null pointer at 6:17, it's not so about niche optimisations but it's because references can't be null in rust even when pointing to ZST, it must be a well aligned non null pointer, and Vec deref as [T], so even if the vec is empty it must provide this, and if it store a null pointer the deref impl would have an if clause to create dangling but well aligned pointer, and you wan't unecessary branches in a function called in pretty much all vec operations, so it's more about performances than memory usage, pretty much all heap allocated data structure do the same thing for ZST, don't allocate for 0 bytes, but still create a valid reference.

    • @_noisecode
      @_noisecode  Год назад +5

      All of this is true! But all of the rules you’re talking about (the strict requirements on the values of references etc.) were created for a reason, which is to allow the compiler to do things like layout optimizations (among other types of optimizations) when it sees references. So I think we are ultimately making the same point, when you consider _why_ Rust doesn’t allow non-null references. Your comment has some great added detail about what Vec has to care about internally; thanks for pointing out the Deref thing, that’s a super interesting point!
      Edit: eh, I guess there’s that whole “memory safety” thing too or whatever that means references need to be non-null and properly aligned. ;) I’m so used to it I take it for granted.

    • @Baptistetriple0
      @Baptistetriple0 Год назад +2

      @@_noisecode sorry if it wasn’t clear, I did not say it was not for niche optimisation, but this optimisation is more an added bonus of the original advantage of a dangling pointer. If I recall correctly don’t C++ also require references to be non null? I’m pretty sure null ref is UB in cpp too

    • @_noisecode
      @_noisecode  Год назад +6

      Yeah I think I got what you're saying! I'm saying that it seems to me that the _reason_ that Rust requires e.g. an empty &[T] to still be non-null is for layout optimizations. Rust could've made the design choice that an empty slice was allowed to be null (meaning Vec could store a nullable pointer), since there's nothing to read from anyway; but instead it requires that it dangle so that all zeros remains a special value in every case. (I'm sure there are other reasons besides layout optimizations that this is a good idea, but layout optimizations is a big one.) Your comment is making me think about this more than I ever really have before so I really appreciate the discussion. :)
      And yeah, null references in C++ are UB. Although IMHO Rust references aren't a direct analog to C++ references; they're somewhere between C++ references, C++ pointers, and a secret third thing (borrow checking).

  • @Wodziwob
    @Wodziwob Год назад +67

    Dang, I'm just learning rust coming from a higher level language background and your videos are amazing. They get me thinking in a more nuanced way about these things.

  • @Argletrough
    @Argletrough Год назад +9

    10:35 std::vector also has a constructor with a specified capacity:
    std::vector v(7);

    • @tialaramex
      @tialaramex 8 месяцев назад +2

      Nope, that's a constructor with a specified size, it's default constructing your type (here int, a default constructed int is zero) to make a std::vector of seven zeroes
      Rust's Vec::with_capacity(N) gets us an empty Vec, but with guaranteed capacity for N items, very different.

  • @pcfreak1992
    @pcfreak1992 Год назад +6

    The best RUclipsrs seem to have the fewest subscribers.. What great content, keep it up!

  • @willaien9849
    @willaien9849 Год назад +4

    In C#, List.EnsureCapacity(int) doubles until the new capacity is equal to or greater than the specified value. It does let you pre-allocate at initialization to a specific value, though (or set capacity explicitly)

  • @lioncaptive
    @lioncaptive Год назад +2

    I come back to your channel repeatedly -- intuitively, it's one of the best ways to learn coding skills. Please keep it coming.

  • @frittex
    @frittex Год назад +2

    I really liked the video. The animations are great and the explanation is clear and easy to follow. One piece of advice I can give is 'don't say what you're gonna show, just show it'. Don't take this as hate, but you shouldn't say that you will show something later in the video. There are situations where you might want to say that, but in most cases, it's better to shape your script so it flows smoothly and shows topics as you talk about them.

  • @Galakyllz
    @Galakyllz Год назад +1

    This video was fantastic. I actually laughed when you were noting how confusing it could be and whatnot at 2:23. No worries, I'm here for it. Thanks for linking to other interesting resources, too.

  • @SigmoidNeuron
    @SigmoidNeuron Год назад +3

    Man, you've been putting out great content. I'm only a Rust beginner, but I love the insights. Looking forward to more videos.

  • @NOBLENAGA007
    @NOBLENAGA007 2 месяца назад

    You MUST make more video like this; On writing efficient C++ code, common and niche pitfalls, and best practices.

  • @lisinsignage
    @lisinsignage Год назад +5

    Excellent, these days I'm mostly programming in C#, and this applies as well 👍

    • @theAcum
      @theAcum Год назад +5

      C# also have 2 functions for reversing its List type
      -Setting Capacity will force reallocation of the array to an exact size
      -Calling EnsureCapacity() will grow the array exponentially till it reach the needed size

  • @robert36902
    @robert36902 6 месяцев назад

    Nice explanation, especially how we can avoid the problem by calling the right function on the vector that adds all elements at once!

  • @catte_6376
    @catte_6376 Год назад +4

    amazing video, made me want to learn rust + beautiful usage of manim

  • @mchammer5026
    @mchammer5026 Год назад +1

    I'm glad this got recommended to me, I hope your channel grows and attracts more interested viewers. Peace!

  • @NOBLENAGA007
    @NOBLENAGA007 2 месяца назад

    Dude... I swear this is the best coding channel I have ever seen. And I base that solely on this single video.

  • @callysibben416
    @callysibben416 Год назад +3

    This was really, really good. Well presented and articulated. Also an interesting topic, I had no idea about vector optimizations, I just assumed each push was a new allocation, so I absolutely would have made that same mistake

  • @Zakru
    @Zakru Год назад +1

    I feel proud of myself, as soon as I clicked I connected the dots and guesses the exact topic. Not a problem I've thought of previously (if I bulk append, I use proper methods, if I push an unknown number of elements, I don't manually reserve), so this was a great heads-up, and the real-world example and precise complexity calculations were great.

  • @smallfox8623
    @smallfox8623 Год назад +11

    I love these types of videos. Even though I mostly use Javascript and Java day to day it’s still so important to know what’s actually happening under all the abstractions. Good stuff 😊

  • @Runoratsu
    @Runoratsu Год назад +2

    If you know you‘ll be inserting multiple times, it makes sense to check if the class you‘re inserting to (like the one with the AddPoints method) exposes a reserve function itself (or a non-const ref to the underlying vector, etc), then you can work around the AddPoints function‘s reserve by simply doing the geometric reserve yourself before you call that function, since reserve will just do nothing if the container is already big enough (at least in C++).

  • @roberttuck4768
    @roberttuck4768 Год назад +7

    One thing not touched on here is that preallocation can be useful when you need to allocate a large number of arrays and you know the size of them is smaller than the minimum default allocation. Obviously it's not so relevant to API implementors.
    But there are many tradeoffs esp if the array size changes later. In general it's best not to call methods like reserve() unless profiling shows that you benefit from the optimisation

  • @kRySt4LGaMeR
    @kRySt4LGaMeR Год назад +3

    really good video. you're such a good presenter and very eloquently showcased the issue and the solution.

  • @JeremyChone
    @JeremyChone Год назад +4

    Very nice video and sensible advice! Thanks!

  • @gingertimelord9334
    @gingertimelord9334 Год назад

    woah a lot of times i watch stuff like this most of it goes over my head, this is so well explained thank you

  • @azaleacolburn
    @azaleacolburn Год назад +1

    I love your videos!
    This actually came up for me a few weeks ago!

  • @John-yg1cq
    @John-yg1cq 8 месяцев назад

    One thing I noticed looking at and starting to use a Dynamic Array in C is that appending a single element and a bulk of elements *is* the same operations. It's just if you're appending bulk of N or 1.
    With this knowledge, you can design around bulk only with iterators and everything, and then keep appending 1 as a special operation. This I think communicates well to users.
    Dynamic Arrays / Vectors are quite possibly one of the best general purpose data types ergonomically. So easy and simple too.

  • @RedonoF
    @RedonoF Год назад +4

    Great videos, love the visualisations

  • @mateoordonez9449
    @mateoordonez9449 8 месяцев назад

    Amazing videos! Easy to understand, good food of thought and amazingly straightforward to make my programming better

  • @vercolit
    @vercolit Год назад +9

    Cool video. I'm more of a C dev and don't always know C++ oddities, so it was weird learning that memory allocated with new or new[] can't be reallocated. It makes sense, but it's a shame that such a simple, massive optimisation for vectors isn't available.

    • @Raspredval1337
      @Raspredval1337 Год назад +12

      even if you call realloc in c, it would most likely allocate a new memblock and copy the contents of the old block to the new one. It really depends on the malloc implementation tho, some allocators allocate in blocks of 32/64bytes and can be efficiently reallocated, some dont, some allocate in block only for small enough block sizes and use unpadded allocations for larger blocks. In short, it depends. So always assume that realloc shall make a new allocation. That's actually the reason, why it returns you a pointer, even if it might be the same pointer you just used: cuz it's most likely, that it would return a new pointer instead

    • @SimonBuchanNz
      @SimonBuchanNz Год назад +4

      This is why I don't believe the C developers that claim they're "seeing the real costs" and that C "isn't hiding anything" - your libraries are still hiding plenty. Your compiler optimizer is rewriting everything you write. Even writing assembly, the ops you have are all microcoded and pipelined and speculatively executed - there's no *real* bare metal anymore (maybe the FPGA people?), so you're just picking the language you like most, just like everyone else. Which is fine, just don't get smug about it!

    • @CheaterCodes
      @CheaterCodes Год назад +3

      ​@@SimonBuchanNzHi, FPGA Person here. Just wanted to point out that the synthesis tools do most of the heavy lifting, optimizations, dead code elimination, hardware mapping, timing analysis....
      But I mean, you can kick down this can as far as you want. You will always get closer, c is closer than c++, assembly closer than c, and so on...

    • @SimonBuchanNz
      @SimonBuchanNz Год назад +1

      @@CheaterCodes I assume you *can* pick and place gates individually if you're an idiot, though?
      I've barely looked at hardware design stuff - Verilog and VHDL seem totally crazy to me - so I still think of you lot as "the real space wizards".
      Considering the non-stop pace of hardware security vulnerability reports, perhaps I should temper my idolatry 😄

    • @sinom
      @sinom Год назад

      What exactly do you mean with "can't be reallocated"? do you simply mean you can't call realloc on that pointer? why would you? What you would want is to instead call realloc on a pointer to the data which that object contains, and update any members which need to be changed to keep the object in a valid state. To make this less error prone you abstract that into functions. In the end it does the same you can do in c just in a safer package (e.g. when naturally using the object you won't be able to realloc the data and then forget to update its length variable). The problem in the video also still happens in c when you simply use realloc in a function you hand a T** or a pointer to a struct to and then call that function in a loop, or use realloc in a loop directly. Just C++ and rust do the speed up of amortized costs automatically when using something like std::vector while in C you'd need to use libraries that do it or write it yourself.

  • @TheBitterSarcasmOfMs.Anthropy
    @TheBitterSarcasmOfMs.Anthropy Год назад +2

    I only clicked the video cuz of the cute crab. I was so disappointed the crab did not play a bigger part of the action in this video. Im going to rate this a 2 on IMDB. I want to see more cute crab action in the sequel. Can you makeba crab prequel?

  • @mordofable
    @mordofable Год назад +1

    3:50 for setting up a TArray to only allocate on the stack, and not touch the heap, you can use the second template argument to specify a TInlineAllocator, with a set number of items. Any number of items added beyond that however do end up on the heap.

    • @_noisecode
      @_noisecode  Год назад +1

      This is an awesome tip for everyday Unreal code! SBO vectors are super useful and I sorta wish I'd mentioned them in the video. Though unfortunately this trick only helps to optimize parameter passing if the API is generic over the TArray's allocator, which USplineComponent::AddPoints for example isn't. So the provided TArray has to use the default heap allocator.

    • @mordofable
      @mordofable Год назад

      @@_noisecode Since it's a const ref, would it not simply use whatever the original TArray allocator has set up for it? As far as I understand at least (and I could be wrong here), once you've added the items with an inline allocator, the TArray simply is keeping track of the pointers to the stack locations of the items. Then the loop inside USplineComponent::AddPoints would just have reference to those locations from the stack.

    • @_noisecode
      @_noisecode  Год назад

      The allocator is still part of the TArray's type, though. `TArray` is a different type than `TArray` (which, due to a defaulted template argument, is really `TArray`). So passing `TArray` to a parameter of type `const TArray&` won't compile.

  • @rafagd
    @rafagd Год назад +3

    I always try to reserve a lot upfront [ie Vec::with_capacity], then let the dyn array do it's thing instead of reserve in a loop.

  • @yourdadsbestfriend7101
    @yourdadsbestfriend7101 Год назад +1

    Amazing video. Great edits and explanations.

  • @ducklingzpl46u36
    @ducklingzpl46u36 8 месяцев назад

    So awesomely explained

  • @dealloc
    @dealloc Год назад +9

    I just love when large C++ projects implements their own data structures of which there are ones provided by the language, making it impossible to get any benefits from optimizations. I understand it can be a compatibility thing, but Rust makes it much easier to avoid this problem; need an iterator that conforms to other iterators in the language? implement the traits. This makes it so easy to integrate different libraries together with the language without having to work with 500 different types and conversions in your main logic.

    • @_noisecode
      @_noisecode  Год назад +13

      I do generally agree with you. Though, the thing that bugs me most about TArray isn't its mere existence--it's defensible that they would want to take full control over their vector implementation and tune it to their exact needs--it's the fact that the API is very different from std::vector. Methods are capitalized, things have different names (e.g. .resize() is called .SetNum()), iteration is kind of strange. It puts a needless barrier, both mentally and syntactically (which is annoying for e.g. templates), in the way of interop. Other libraries like Boost.Container that provide container alternatives do so while trying to mimic the standard API as closely as reasonable, which is a much better approach IMO.
      In Unreal's defense, though, as a codebase it literally predates the C++ standard. There was truly no standard library to use when the project was started. I recall a forum post where Tim Sweeney explains that the reason they rolled their own containers was largely historical and due to this fact. In that same post he mentioned that it might be a reasonable medium- to long-term goal to migrate UE to use the standard containers.

  • @konkitoman
    @konkitoman Год назад

    Really grate video!
    At the beginning i was like how to use reserve is a bad idea, now i understand!

  • @danser_theplayer01
    @danser_theplayer01 8 дней назад

    So the smart automatic allocation for growing a buffer with push() I believe is n log2 time complexity? Since it just doubles the size every time (just left shift the capacity integer once and ask for that much memory, feels nice).

  • @IllidanS4
    @IllidanS4 3 месяца назад

    Sounds like a good rule of thumb would be this: could you replace each AddPoint with AddPoints on a 1-sized array without a performance drop (irrespective of the heap allocation)?

  • @Luxalpa
    @Luxalpa 8 месяцев назад

    Ironically, in Rust I actually had the opposite problem. The vector always doubles in size, which at some point was just too big to ignore (it was wasting 120 MB on a webserver that took a total of 250 MB), so I did some workaround to avoid the doubling in size (primarily using some heuristics over the general size of the data I had).

  • @HoutarouOrekiOsu
    @HoutarouOrekiOsu Год назад

    10 years of both high and low level programming and I've never thought of this situation

  • @KX36
    @KX36 Год назад +1

    examples of how to get geometric growth back onto reserve:
    template
    void reserve_geometric(std::vector& vec, size_t n){
    vec.reserve(std::max(n, vec.capacity() * 2));
    }
    template
    void reserve_geometric(std::vector& vec_to, std::vector& vec_from){
    vec_to.reserve(std::max(vec_to.size() + vec_from.size(), vec_to.capacity() * 2));
    }
    I'm not saying it's better than the insert method shown in the video. I'm just giving example of how trivial it is to e.g. modify the reserve line in Unity AddPoints() to get this behaviour, if you want it. It's probably better than the method in the video which uses .resize() to grow geometrically because .resize() default constructs all the elements which might be expensive, depending on the type.

  • @hannahnelson4569
    @hannahnelson4569 Год назад

    This is great! Thank you for informing me of this!

  • @natehacker1077
    @natehacker1077 9 месяцев назад

    It might be worth a little bit of constant overhead to allocate *at least* a factor of the current vec capacity, but if the numbers of objects you're adding is more than that factor then allocate some extra in the same allocation. Also just exposing a reserve function for the collection in the API could be useful

  • @gamergt3095
    @gamergt3095 Год назад

    Hm, why it wouldnt be better to resize only if the length is < than the value you want to resize it to and if grown factor was still small?
    That way would be optimised for both cases.

  • @0LoneTech
    @0LoneTech Год назад

    6:08 While it's common for a null pointer to have a bit representation of all zero, that's not what C or C++ specified last time I checked. They do specify that casting an integer zero to a pointer will produce a null pointer, but not what e.g. "void *p; memset(&p, 0, sizeof p);" will leave p pointing at, nor u.p after "union {void *p; uintptr_t n;} u; u.n = 0;"

    • @_noisecode
      @_noisecode  Год назад

      You're 100% correct--the bit pattern of a null pointer is not technically specified. In practice it's all zeros.

  • @japrogramer
    @japrogramer 11 месяцев назад

    How is the reserve fn different from creating an empty vector with a capacity?

  • @tmlen845
    @tmlen845 5 месяцев назад +1

    The same could have been said in a few seconds.
    Of course the purpose of reserve() is to avoid doing many reallocations, and it will be counterproductive if you call it repeatedly while adding elements...

  • @davidreis5086
    @davidreis5086 Год назад

    It's a good point, but why insert such small array into the spline? Can't you just run the loop and store the items in a new array before sending to spline, or just use the addpoint multiple times instead?

  • @yash1152
    @yash1152 Год назад

    11:40 how did u do syntax highlight for pseudocode??

  • @kippers12isOG
    @kippers12isOG Год назад

    This video gave me life. Thank you sir. Instant sub

  • @shadamethyst1258
    @shadamethyst1258 Год назад +3

    Your video should be linked in Rust's documentation for Vec::reserve, that cleared up why we also have the reserve_exact method

  • @HrHaakon
    @HrHaakon Год назад

    As an example of growth rate of a vector, Java will multiply by 1.5.
    An empty ArrayList (don't use Vector, it was a mistake, ArrayList is the one that's actually good) will by default have a capacity of 16.
    If you wanted a capacity of 7, you'd ask for new ArrayList(7);
    Oh yeah, and reserve is called ensureCapacity.
    Just as an example of what Logan is talking about at around 10:00. Different implementations of these ideas give different tradeoffs depending on what you want to accomplish.

  • @Tangorithm
    @Tangorithm 8 месяцев назад

    10:34 “Feed two birds with one scone.” 😂

  • @cunningham.s_law
    @cunningham.s_law 11 месяцев назад

    what if the unreal function called reserve with double the size and only if the current capacity + the elements you want to add is greater than some threshold
    this would be fine to call in a loop

  • @faeancestor
    @faeancestor 9 месяцев назад

    Very informative

  • @zamf
    @zamf 8 месяцев назад

    Since C++'s reserve can allocate more than the requested size wouldn't it be better to implement a version where the reserved size is always a power of 2? For example, if you call reserve(9) you get a capacity of 16. This way you get an exponential growth of the vector and avoid this problem. Of course, this means the vector won't be optimized memory-wise but this is one of the assumptions with vectors in C++, that the capacity is usually larger than the size. Maybe a new reserve method can be defined in the C++ standard with this exact behaviour.

  • @norude
    @norude Год назад +1

    This video is perfect!

  • @tachoblade2071
    @tachoblade2071 Год назад +2

    Now i know you have a particular target audience and you have a sort of "bare minimum" expectations from your audience, a base line of knowledge so that they can understand the video. But i believe your absolutely wonderful animations and teaching are limited by this. Like ofcourse you'd expect your audience to know about something like move, copy, iterators and stuff like that, to explain everything would be frankly redundant and way to time consuming. But i for one didn't know about them, i had to Google them to understand better and more deeply and there are many points like this in the video. Not so many that i'd be just googling everything and ruining my viewing experience (and also possibly missing the overall main point of video), but there are are some where i personally didn't have enough knowledge to say, yes i understand what he just said.
    What my point is, is that maybe lower this "barrier of entry" a bit more. You explained everything amazingly, don't get me wrong. Even if I don't fully know methods like copy and move and stuff like iterators and stuff deeply, it wasn't so much of a hinder in my viewing experience but i do believe that there are topics that deserve the same ground/fundamental/ fully basic treatment that the entire process of push_bck got.
    Like how grant Sanderson makes his video, he is mindful of the fact that people might not know even basic stuff (not too basic but to an extent) and makes a point to address them but also goes deep enough in complex enough topics that it'd not be just some kindergarten lecture but rather, a full college lecture. There's this really amazing balancing act of going deep enough in a complicated topic and still using basic fundamental ideas to build these complex topics.

    • @atiedebee1020
      @atiedebee1020 Год назад +3

      I think that if one doesn't know what copy is this video isn't made for you

    • @tachoblade2071
      @tachoblade2071 Год назад

      @@atiedebee1020 you're absolutely correct, there are some basic things audience is expected to know to understand the video. But like how he explained the push_back loop so illustratively and just basic fundamentals, i believe that kinda treatment for more things would lower the "barrier of entry". Ofcourse, a good discussion wouldn't really just be all drawings and conceptual understanding, it would require code too, but i believe if he just takes a moment to clear any functions/methods used, that would be amazing

    • @_noisecode
      @_noisecode  Год назад +5

      I really appreciate this thoughtful feedback! This is something I think hard about how to balance; I really like mentioning all the little tidbits but also don't want to spend long stretches of time explaining them all and detracting from the main point of the video (and making it so it's 45 minutes). My goal is instead to briefly describe them using enough key terms that, if someone doesn't understand, they have some solid words/phrases to google. And the comment section is always open for any follow up questions!
      I am still finding my groove though and I'll be glad to keep this perspective in mind. Thanks for sharing and big thanks for watching :)

  • @isaiah7389
    @isaiah7389 Год назад

    What is the difference between reserve_exact and with_capacity, in rust?

    • @_noisecode
      @_noisecode  Год назад +1

      Vec::with_capacity creates a new Vec, whereas .reserve_exact operates on an existing Vec. Vec::with_capacity(n) is equivalent to:
      {
      let mut v = Vec::new();
      v.reserve_exact(n);
      v
      }
      Edit: actually, Vec's docs are inconsistent about whether with_capacity gives you exactly the requested capacity or whether it might give you a little more. Best way to know for sure would be to dive in and read the implementation!

  • @rhnirsilva652
    @rhnirsilva652 Год назад

    dude,,,, what a BANGER

  • @rogo7330
    @rogo7330 Год назад

    While programming in C, the most natural way to operate on memory is either completly hide internals and do stuff by handlers (pointer, integer, whatever), or not mess with memory at all and just operate in functions with what user provided. For example, standard C library function 'getline' is scarry to work with because it only works with memory that allocated with the same standard 'malloc' and 'realloc'. It would be rather neat if it worked like 'fread' but with caveat that it stopes reading when encounters specified delimiter (or just NL), but it is not.

    • @MatthijsvanDuin
      @MatthijsvanDuin Год назад

      fgets() does exactly what you're asking for, but the obvious downside to such an interface is that you either need to know the maximum line length or you need to be able to process a long line in chunks. The whole point of getline() is to read a full line _without_ any specific limit on line length (apart from having to fit in memory), and for that purpose its design seems pretty reasonable.

  • @mikeyangyang8816
    @mikeyangyang8816 2 месяца назад +1

    This not true for most reserve(), the std::vector::reserve() doesn't reserve exactly n elements (as you said). That is also a fair assumption for unreal's reserve. A reserve can allocate 1.5 or 2 times the input size (or some other factors), depending on the implementation of the standard library. In fact, c++'s reserve is the same as the rust's reserve, and rust's reserve_exact() is a unique feature. If you call reserve n times in a loop that pushes n elements, it will perform the same as just pushing without reserve. This might not true for different things. For example, c++ won't reserve 2 times the size of the inout if your main memory has less than the n*2*your-object-size. Or if your object's size on the stack (or heap in this case) requires like an ungodly amount of memory, it might reserve more conservatively.

  • @sokzpieprzu
    @sokzpieprzu Год назад +1

    God, why was my first thought "reverse()", as from for example javascript

  • @rokkuform
    @rokkuform 10 месяцев назад

    we need more juniors teaching other juniors how to not write code

  • @jacksonhenry1489
    @jacksonhenry1489 Год назад

    Really cool!

  • @FlorianBerchtold-e2v
    @FlorianBerchtold-e2v Год назад

    What about calling resize() in a loop?

  • @lemongrasscap8693
    @lemongrasscap8693 Год назад

    you better keep making videos these are to good.

  • @normiephobe
    @normiephobe Год назад

    i don't buy it at first, seems counterintuitive to have resize as exponential growth, and reserve as linear, after a bit of testing, it just kinda clicked for me, the definition of "size" and "capacity" is slightly confusing, a more intuitive way of saying it, is "element count" and "memory size", which, funny enough, it is the same as the former, but applied in a slightly different way, the resize() method might as well just say, decrease_element_count_or_expand_capacity_to_fit_initialized_element(), the method reference pages doesn't seems to help it, only the line
    "Vector capacity is never reduced when resizing to smaller size"
    thank you for this informative video, never wrote an API like this, since calling container method other than assign in a probable loop always sounded dangerous.

  • @danielchin1259
    @danielchin1259 Год назад

    The ONLY notable benefit of exact reserve, compated to inexact reserver (e.g. `resize`), is memory compactness. If not coding for embedded platforms, do prefer inexact reserves.

  • @444limm
    @444limm Год назад

    someone told me that reserve is bad for certain conditions. But i wasn't sure what they meant. After watching this i know exactly what they meant now.

  • @Lohkat
    @Lohkat Год назад

    I generally use resize because sometimes reserve feels like it's changing the data already there...

  • @watcher8582
    @watcher8582 Год назад

    tl;dr vector push implementations are "secretly" optimized for cases where you call it repeatedly in a loop.
    So if you got a loop and manually resize every time, you make it worse than it would have been.

  • @ghfhhable
    @ghfhhable Год назад

    lets say the memory increases by a factor of 2. And you want to add 100 points 100 times (all different) which method would be faster?

  • @grahamcracker1234
    @grahamcracker1234 Год назад +3

    commenting for the algorithm

  • @DevWolf59
    @DevWolf59 Год назад

    3:47 You can get the pointer of a statically defined array, if the size is known you don’t have to dynamically allocate it

    • @MatthijsvanDuin
      @MatthijsvanDuin Год назад

      The method requires a TArray which will dynamically allocate the storage for the elements.

  • @ianwong1109
    @ianwong1109 Год назад

    Correct me if im wrong, isn't the actual problem is that you are reserving a marginal larger size of the original array in a loop? If you are reserving a constant size/a much larger size than original vector, I couldn't see anything wrong. So I don't really think its a loop specific problem but rather, reserving with a tiny marginal extra size.

  • @dzuchun
    @dzuchun Год назад

    I love rust. I guess, this was a reason to make a separate reserve_exact function 😊

  • @vcankeklik
    @vcankeklik Год назад +1

    Similar to building strings without a StringBuilder or buffer, imo

  • @BR-lx7py
    @BR-lx7py 8 месяцев назад

    @9:30 shouldn't that be O(log n) complexity, since log(n) of the pushes will trigger a memory reallocation? Calling it O(1)+ sounds like a marketing gimmick ;)

  • @PaulEmsley
    @PaulEmsley Год назад

    Good graphics, excellent material well presented. However, the bad/terrible audio makes it a difficult watch. It was popping, clipping and muffled. I suspect that you can do better. Anyway. Thanks, this is the first time I've seen one of your videos, I'll now go see what else you have.

  • @Yupppi
    @Yupppi 11 месяцев назад

    Somehow you made vectors much less annoying/scary than when I learned them years ago and just smoothly forgot, because they made me think too much of processing strings through, which was my least favourite puzzle in c++. But honestly seems like everyone is using vectors these days instead of arrays etc, and they seem to have quite a few convenient tools and features that can't be matched.

  • @jbrownson
    @jbrownson Год назад

    Excellent videos

  • @maninalift
    @maninalift 10 месяцев назад

    I can see what's coming, it's going to bypass the preallocated memory driving strategy. Obvious once you realise that reserve only reserves the exact amountv requested, but easy to imagine you are doing a good thing by calling reserve.

  • @jenaf4208
    @jenaf4208 Год назад

    I lile the version where you can reserve exact and/or revse at least

  • @intercept9510
    @intercept9510 Год назад

    awesome video!

  • @john.dough.
    @john.dough. Год назад

    what a brilliant channel!!! omg :)

  • @porky1118
    @porky1118 Год назад +2

    11:00 Btw, I already know, where this is going to.
    If we always reserve 8 elements, we have to reallocate once per 8 elements, instead of once per doubling the number of elements.

  • @GottZ
    @GottZ Год назад

    this is strange. why would you copy contents from small to bigger by hand if you can let the kernel do it for you through a memory re-allocation.
    sometimes the increased memory you request still fits into the already used memory page (cause padding duh)..
    so calling realloc() in c, could result in increasing the allocated aligned memory without a single copy instruction.
    since you don't mention this at all, I wonder if rust and unreal really do not make use of this behavior..

    • @D0Samp
      @D0Samp Год назад +3

      Typically, only large enough allocations beyond a few kilobytes use a dedicated memory mapping that is not managed by a shared allocator. And your allocator still has to extend those mappings with additional pages on demand and move them if it runs out of address space, same as if you had no OS with virtual memory support at all. Only the stack is usually auto-extended by the operating system.

  • @GuatemalanWatermelon
    @GuatemalanWatermelon Год назад

    good

  • @ChrisHinton42
    @ChrisHinton42 Год назад

    In reply to 3:55... UE has TArrayView... but for some reason it isn't used very often in engine APIs. I have no clue why. I use it as a drop-in replacement for const TArray&, unless I am delegating to an API that requires a TArray.

    • @_noisecode
      @_noisecode  Год назад

      Yeah it’s a great tool, but yeah, I don’t see it used much (kinda hence my wording about there not being a tool like this in UE’s “core design”). You also see ptr+length pairs used here and there but not too often. You could imagine maybe it’s tricky to support in BlueprintCallable APIs (and potentially opens up Blueprints to a new class of dangling-arrayview bugs?) and maybe it has just never gained much footing in the ecosystem for that reason, though it feels like it could still be used in engine-internal code more than it seems to be.

  • @monkyyy0
    @monkyyy0 Год назад +5

    > two birds with one scone
    no

  • @suhedheglobdefne6739
    @suhedheglobdefne6739 Год назад

    Well, kinda. For general API its better to use reserve to have AT LEAST n elements. So the exponential growth will be maintained. Something like
    While (current_size < needed_size) {current_size *= 2;}
    vec.reserve(current_size);

  • @GnomeEU
    @GnomeEU Год назад

    They should just put a hint that this allocates / copies the whole array every time you call it.
    Then it would be really easy to understand.