Casey Muratori | Smart-Pointers, RAII, ZII? Becoming an N+2 programmer

  Рет қаралды 39,702

xDahl

xDahl

Күн бұрын

Пікірлер: 302
@xDahl
@xDahl 26 күн бұрын
If this video was of value to you, I highly recommend giving this one a watch: kzfaq.info/get/bejne/f9ifg7RimpaqZpc.html
@k1ngjulien_
@k1ngjulien_ 22 күн бұрын
hillarious, i was just about to make a comment about exactly that talk lol
@linus7529
@linus7529 Ай бұрын
I think I might get a tattoo that says, "It's important to remember that you are also stupid."
@surters
@surters 18 күн бұрын
Reminds me of a colleague, who says if life sucks, remember your also ugly, so now I need 2 of those ...
@tiko-
@tiko- Ай бұрын
One of the Go proverbs is "Make the zero value useful." Rarely do I see people actually follow through on this but when you do it can be quite nice. (Yes, I realize Go is a GC language so it's kind of irrelevant, but I thought it was an interesting application of "ZII").
@iestynne
@iestynne Ай бұрын
I've heard Casey and Jon complain about this many times, but this is by far the best summary of the problems created by standard allocation and error handling methods. I honestly always thought I was being dumb when I didn't 'get' smart pointers or RAII or exception handling... unlike most other concepts I've encountered, I never had that 'aha' moment where I saw the utility of the framing and started integrating it into my thinking. It just never seemed like it... really solved anything... yet it led to a verbose and confusing mess that I couldn't confidently reason about. So I just ignored it and felt guilty writing code that didn't have any error handling in it (because the logic was designed such that it wasn't needed). I've worked in games a lot and at the engine level every company I have worked for has had a low level architecture that uses the N+1 approach; a bunch of coarse systems each with its own custom pool/arena allocator. Performant, reliable, easy to understand. But the gameplay code is always a buggy mess of N+0 code with random error checks sprayed everywhere! (each one being the 'fix' for some prior bug ;) )
@xDahl
@xDahl Ай бұрын
I had a similar but different problem thinking I was dumb when moving from the N to N+1 stage. Any time I was trying to write code, I would do the N stage despite feeling like I was trying to control an uncontrollable mess, and guarding against all sorts of obscure error points and handling them. And I'd feel like I was just dumb compared to others who did manage it. My "solution" at the time was to basically just write everything into self contained libraries that were pieced together by a main loop; but even that didn't work and was hard, especially with shared data. Easier, sure, but hard. But every now and then I'd steer away from "idiomatic" N ways of doing things and _slightly_ onto the path of N+1... I'd say I mostly hit the N+0.4 stage... And despite seeing its effects where things became easier to manage, and error handling collapsing down to just a few points, I just felt like it just wasn't "proper programming"; and that despite the objective better results, I should return to stage N because it is "proper" and what people teach. At some point I almost came up with a pool allocator for a project, and while I could see that it was better, I couldn't quite figure out the general trend that made it better or how to continue that trend, but I knew that the way I had done things before objectively sucked, and there *had* to be a better way. Thankfully, having found Casey and moving from C to Odin, I finally figured out what I was doing wrong, and this video finally clicked. I have a friend now who's getting into programming in Java, and the biggest advice I gave to him was to understand what problem he needs to solve, and once that gets solved, no matter how "wrong" or weird that solution is - stick to it _until_ the problem changes. Write the simplest thing you can, and if it works, then it works and is *done*. It's not wrong if it works and solves the problems you care about.
@oscarfriberg7661
@oscarfriberg7661 Ай бұрын
RAII shouldn’t have any extra error checks. The only thing it does is to ensure all resources are properly freed when the object is destroyed. For example, if you’re implementing a file handler or a network socket, you most likely need a .close() method to ensure there are no dangling handlers. With RAII, that .close() method is the destructor. Same thing with heap allocations. If your class does any heap allocations, then you just ensure all heap allocations are freed inside the destructor. No need for a separate .free() method that must be manually called. The destructor is always called when the variable goes out of scope. No risk of dangling file handlers just because the function returned early. If you’re handling multiple resources at the same time, then RAII ensures all resources are always freed in the correct order. If you’re only relying on stack allocations, then you don’t need to do anything extra. It’s already RAII. You’re probably using RAII without realizing it.
@Muskar2
@Muskar2 Ай бұрын
@@oscarfriberg7661 You can argue semantics like that. But the RAII I know is specifically about utilizing constructors and destructors, and is rooted in individual-object thinking. I've seen some proponents argue that the stack is also RAII or that linear allocators and memory arenas are also RAII, but I think that's a misrepresentation and muddling of the term. RAII is an idiom meant to prevent resource leaks of individual things. Pushing/popping to the stack is integrated in assembly language and precedes RAII. Also, linear allocators and memory arenas don't need constructors/destructors. E.g. a scratch arena could be 50MB (growable) that is zeroed once per frame, and thus there's never a concern about lifecycles of the memory pushed unto it. Talking about RAII in that context makes very little sense.
@simonfarre4907
@simonfarre4907 Ай бұрын
​​@@Muskar2no you are wrong or whoever told you that was wrong. RAII is about invariants and being able to enforce them. A class or type that is backed by a arena allocator, uses RAII to acquire/release its use, for instance, atomically reducing the use count in the destructor. The point of RAII is also to have a single place of exit so you don't have to consider it at every call site, it's automatic. Say you have activation frame A, and it does some things, until you have frames A, B, C, D, E, F, so 6 stack frames. In A you have created an arena allocator for everything below A. All the objects under this frame, use this arena. Their destructor may be designed to be a no-op, it may do something else, depending on if the arena can re use memory, but it will happen automatically. That's the point of RAII that, between initialization and destruction (even if said destruction is a no op and does nothing) class invariants are upheld. Even Casey gets RAII wrong, though he is right that it's thought of like he describes it, which is the *real* problem. You can have RAII and what Casey says and they will work perfectly together. Perfectly.
@Muskar2
@Muskar2 Ай бұрын
​@@simonfarre4907 RAII literally means Resource Acquisition Is Initialization, and stripping all the interpretations of that, it means ensuring all 'resources' of an 'object' are allocated and initialized before a programmer uses it. Which inherently is an individual element OOP concern, where your 'objects' points to their dependencies. That's not useful to think about if you simply have data that needs no initialization and has no 'resource' dependencies. E.g. when you create a bunch of int and float variables in a function (pushed on the stack), RAII makes no sense to talk about, because there's no concern that there's unallocated memory or a unknown pointer chain with a null pointer somewhere. It's all known by and visible to the programmer. Similarly, all individual elements that are allocated with an arena have no memory ownership concerns, because it is managed entirely by that arena (and the programmer always has a ballpark idea about the constraints of that arena's strategy). You could argue the arena is sort of RAII, because it needs to ask the OS for memory to be initialized, but that is a tiny dependency that both happens infrequently and isn't an interesting aspect of the program. When you say 'atomically reducing the use count in the destructor' what destructor are you talking about? A (scratch) arena for small short-lived allocations have no destructor for individual elements, the entire thing is just reset once per main loop and you just freely push things onto it. And the scratch arena itself typically lives for the lifetime of the application, so there's no need for any destructor. On the other hand, arenas for long-term data with well-defined ownerships may have the equivalent of a destructor if there is a realistic memory limitation and that its lifetime/scenario is no longer relevant to the application, but it is not a common thing and you still don't have destructors for individual structs, arrays or strings/buffers, because it simply isn't useful to think of the lifetime of each element - only of the batch/arenas. Putting the ownership concern to the individual element is the problem that multiplies all these concerns. Having it be automatic is beside the point, and can be done without destructors. Considering a stack-like arena, one arena will not arbitrarily change between freeing with no-ops, popping the stack-like pointer to reuse memory or also zeroing that memory. It will typically be well-defined behavior for that individual arena and by pushing individual objects to that arena, you also imply that its ownership and lifetime is equal to the behavior of that specific arena.
@walter0bz
@walter0bz Ай бұрын
Some good points but too dogmatic IMO. It is possible to have a mix, based on the number of things in a system. Writing a 3D modeller (or sandbox game with user content?) - consider the Number of viewports.. Number of meshes.. Number of vertices? The approach at each level would be different. I’m well aware of the cache issues having been through PS2, Xbox360/PS3 era. I still want all the tools available. One can build RAII containers that hold pools and so on. Having a language handle a range between rapid dev and fully optimised stops you needing more pain mixing different languages in a workflow. Too harsh on rust aswell - the borrowchecker prevents the pointer rats nest. Enum/match makes writing message queues to batch up changes easier. Rust is set up for writing code that’s easy to parallelise.
@rudybanerjee6367
@rudybanerjee6367 Ай бұрын
Hell yes, this is awesome
@allNicksAlreadyTaken
@allNicksAlreadyTaken 10 күн бұрын
He says it applies to everything, it is very specific to video games and he is privileged to not have to work with herds of people that aren't that good at programming. Most of my time writing code is making sure it cannot be misused and there is no way to use it incorrectly. I worked in telecommunications and sadly, as with most software jobs, most coworkers are incompetent. If you write code that does as little as possible and is super fast, but is effectively broken after only a few other people have touched it, phone networks can go down and you might get woken up in the night or lawsuits might happen. Most software that is used is written by dozens of people, many of which are simply too dangerous to be trusted with code that breaks easily.
@zcizzorhandz5567
@zcizzorhandz5567 10 күн бұрын
This guy is better at articulating everything I've been trying to say in the past decade and I don't seriously consider myself a lower-level dev. The same applies to all software development.
@mateusvmv
@mateusvmv Ай бұрын
zero initialization is initialization garbage collection is moving a stack pointer back up after the function ends
@tugbars4690
@tugbars4690 Ай бұрын
well put!
@Muskar2
@Muskar2 Ай бұрын
I assumed it was a joke I didn't get, until tugbars said 'well put!'. I'm not sure what you mean. The RET assembly instruction has moved the stack pointer since its inception with 8086. Garbage collectors are vastly more complicated than moving a stack pointer.
@tugbars4690
@tugbars4690 Ай бұрын
​@@Muskar2 The idea is to use scratch space as much as possible rather than allocating new memory on the heap or using static arrays. When modeled correctly, dynamic allocation can often be avoided. However, it's important to note that this may not always be possible.
@mateusvmv
@mateusvmv Ай бұрын
@@Muskar2 Indeed, as tugbars said. More specifically, you can avoid dynamic allocators and use only arenas whenever your program is single core and not concurrent. That is, use two arenas, one for return and another for scratch, and alternate between them as needed.
@Muskar2
@Muskar2 Ай бұрын
@@mateusvmv I don't disagree. But what does scratch space has to do with the statement 'garbage collection is moving a stack pointer back up after the function ends'?
@thebutlah
@thebutlah Ай бұрын
The pressure the rust borrow checker gives you *against* style n is exactly what taught me to adopt style n+1. I would not have gotten there as fast without it.
@Wodziwob
@Wodziwob Ай бұрын
This is a dang good point. By forcing you to think about the trouble tiny lifetime focus causes, it really does encourage you to think bigger faster about things like arenas and such. But I still value the idea of rust/raii for stuff like dropping file handles and channel senders and receivers. And rust has really empowered me, someone who learned using higher level languages, to delve into lower level programming-- so I also credit it for that. But I'm not beholden to any language. I'm inspired by Casey's points here
@lm-gn8xr
@lm-gn8xr Ай бұрын
How do you do n+1 style in Rust ?
@Wodziwob
@Wodziwob Ай бұрын
@@lm-gn8xr Well, in some sense you get some stuff that is "allocated together" by default, by using things like Vec. And for some other stuff where you'd otherwise get a bunch of allocation, you can use an arena allocator crate like bumpalo. And then in certain cases you can simply just optimize your types to store references to things instead of owning them. E.g. MyThing
@DenshinIshin
@DenshinIshin 22 күн бұрын
@@lm-gn8xr With user defined lifetime or / and custom allocators for all the resources that share the same lifetime. What is defined in n+1 is just group thinking and it's not that complex to do in Rust, without having to allocate on the heap each single elements that shares the same lifetime like someone stuck at n. You reserve the space for n resources, and make it last the whole program, the whole frame, etc. You end up with one big allocation, and one big destruction, wasting less CPU time and introducing less failure points than someone allocating things individually.
@bryanedds8922
@bryanedds8922 21 сағат бұрын
All you're doing is circumventing the protections that you paid so much to get.
@insentia8424
@insentia8424 12 күн бұрын
Funnily, the n stage was a huge wall to me. There were so many tiny things working together that I couldn't visualize it, and thus couldn't understand it and it felt extremely demoralizing and like I was too dumb for programming. Only when I ditched trying to understand that stuff and go about it other ways (largely inspired by Casey's handmade hero and other talks about programming), it all started to click and make far more sense, because I could finally visualize what is supposed to happen. Whether I'm at the n+1 stage currently... I don't know. There is probably some of the n stuff in there while prototyping a feature just because it's quicker to check if it even works.
@zacharyquinn3676
@zacharyquinn3676 Ай бұрын
Second most important piece of programming advice - what’s the first?
@xDahl
@xDahl Ай бұрын
I have been thinking about that for a while, and I still haven't quite decided, I just know that this video was the second most important and influential to my development; even tho I didn't get it at first.
@Kknewkles
@Kknewkles Ай бұрын
The first might be don't overplan things.
@mattmurphy7030
@mattmurphy7030 Ай бұрын
Don’t be an architecture astronaut
@logicaestrex2278
@logicaestrex2278 Ай бұрын
​@@mattmurphy7030I'm not sure I understand what this means
@mattmurphy7030
@mattmurphy7030 Ай бұрын
@@logicaestrex2278 there’s literally a Wikipedia article
@sunkittsui7957
@sunkittsui7957 Ай бұрын
Very interested in the ZII pattern that Casey mentioned at the end. But there is something that I am confused about it. Does it apply to all allocations or just things that have a specific lifetime and is guaranteed to not overlap (i.e. for each frame). Because if I try to grow a long lasting dynamic datastructure like a hashmap or linkedlist, then writing to a shared pool of zeroed out memory will not really work.
@Muskar2
@Muskar2 Ай бұрын
ZII applies to all allocations. What specifically is your concern with long lasting dynamic data in regards to zeroed memory? Typically the issues with long lasting dynamic data is high fragmentation once you approach the memory limits of the target system. But first realize that such concerns often are grown out of unfounded fear that the data will grow beyond your realistic expectations. E.g. having 20 GB of long lasting dynamic memory when you know only a few hundred MB is typically needed for the scenario in question, then a memory arena that lasts the lifetime of the program will typically be fine. Anyway, long-lasting dynamic memory-limited data is what free lists handle. And if you actually determined you need a free list, try to see if you can make the things into fixed size entities (aligned to 64B-factored boundaries for cache line efficiency), which makes fragmentation much more trivial to handle. On a sidenote: Hopefully you know not to use a linkedlist in production unless you're a senior - because they're rarely the best thing to do.
@treelibrarian7618
@treelibrarian7618 Ай бұрын
seems to me that n+0 thinking is a consequence of the language design making allocation of small objects easy enough to become a default way of constructing things. personally I tend to avoid the whole "pointers→structs of assorted data" type situation if possible, since this reduces pipelineablility, data-streamability, and vectorizability of operations. language design needs to move on from this paradigm since that's not how CPU's have liked to work since about 1995.
@msqrt
@msqrt 18 күн бұрын
You use arenas so that not every operation can fail, but then you use growable arenas so that every operation can fail, but then you just handle that case for each failure by returning a stub to then reinvent null pointers..? The point about thinking about grouped operations is very good though.
@yessopie
@yessopie 15 сағат бұрын
I think there are still many situations where "N" programming is by far the simplest and best approach. Like if you are just parsing a JSON config file on startup... Just use std::string, which is RAII. You can get huge performance improvements if you write some bespoke memory-arena string routines if you need to parse a very large log file or something. But for many things, that kind of performance just doesn't matter. j_blow said something similar about optimizing the loading in Doom... At first he thought John Carmack was an idiot for writing such inefficient code. Then he realized that you wouldn't actually gain anything by optimizing it, so Carmack did the right thing by keeping the code simple.
@unimatrixz3ro226
@unimatrixz3ro226 9 күн бұрын
I really like this content, more like this plz!
@stefan000
@stefan000 Ай бұрын
I'm curious about the 'ZII' he's talking about, he states he has omitted a lot of error code by accepting that 0 by default is a valid initialisation but for any complex 'object' let's say some parser object _some_ checks will still be needed to be there like a check if we loaded the given data into memory, etc... If he's talking strictly about returning a resource from a getter style method I see what he's talking about but in other scenarios you still have to check stuff if something is initialised with data.
@Botondar
@Botondar Ай бұрын
If a failed load returns a 0 count buffer then the parser should by default think it has reached the end of that buffer in the failure case (i.e. cursor < count is false). There's no need for another check. There are a bunch of similar situations where the all-0 case satisfies the end condition.
@stefan000
@stefan000 Ай бұрын
@@Botondar I see, that makes sense
@neural75
@neural75 15 күн бұрын
​@@Botondar yes, but there are a butt load of cases also where this is not satisfied at all. You open a resource that does not exist and you return a zero initialized handle? really? so the caller has no mean to detect a failure and it can write to that resource (and I guess it will silently say it wrote 0 bytes). The cases where everything converge to the "empty case is equal to the final result" are RARE and even in those cases, often mislead the users. For sure it is not something to be taken as a new and better paradigm to program that fits all.
@Botondar
@Botondar 14 күн бұрын
@@neural75 allowing the 0 case to flow through is completely orthogonal to proper error reporting, you could always have additional values that tell you whether there was an error or what it was. The point is to parameterize the code in such a way such that if the caller code doesn't check for an error, it will logically NO-OP.
@neural75
@neural75 14 күн бұрын
@@Botondar The point is that zero cases treatment are rare and you end up having a lot of error handling anyhow, there is no magic bullet in programming and the first lesson to learn is really to stay away from slogans and a like.
@fromjavatohaskell909
@fromjavatohaskell909 5 күн бұрын
Allocation in advance is definitely preferable and sometimes it is required (like for example in some embedded systems) - but it might be tricky when you write program driven by external data. You don't know in advance resource requirements - for example how much would you need extra memory to parse incoming JSON object? I think we might consider multistage processing - for example some preliminary parser with fixed overhead do preliminary processing and determines requirements for later stages.
@xDahl
@xDahl 2 күн бұрын
Virtual memory on 64 bit systems helps a lot when you don't know how much memory is needed for a task, as you can reserve massive ranges without committing it all. But even then there will be outliers, though I don't think most programmers will have to deal with a 128GB+ JSON file. At that point, something has gone terribly wrong.
@nexovec
@nexovec 29 күн бұрын
tbh. it might even be a very bad idea to try to skip n altogether.
@drygordspellweaver8761
@drygordspellweaver8761 Ай бұрын
This is the most brilliant dissertation in the history of the Universe.
@azgult
@azgult 17 күн бұрын
This is one of the worst takes I've heard in a while. It completely glosses over what your problem domain is, and that not all problems are amenable to the types of groupings we're talking about. This is especially true for programs where multiple things reference the same allocated structure, where it's not possible to statically reason about what may go out of scope if a part of the structure is removed. That actually hinders reuse, not helps it. The fact that reasoning at the element level is also *crucial*. It's the backbone of functional programming, and by forcing reasoning at a more holistic level you set yourself up for failure if your application changes direction, makes it easier to make mistakes about what you think your application will require, and ultimately complicates your overall architecture, all for the benefit of simplifying allocation specifically. This is really the crux to me: It is good to not have to think about allocations. They are a distraction from making code *correct*, and that is usually more important than performance. I also disagree on the argument that this reduces failure greatly. It does reduce the *points* of failure, but they're all homogeneous. Your 1000 allocations of 1kb will fail as often (actually slightly less often depending of paging) to your 1 allocation of 1mb. Even your case of 'literally not failing' is a failure case.
@xDahl
@xDahl 14 күн бұрын
I don't quite see your reasoning, but you mention "functional programming", are you saying that what Casey is advocating for won't work in a functional paradigm specifically? Or is this a more general claim?
@bryanedds8922
@bryanedds8922 22 сағат бұрын
@@xDahl It's a general claim, I believe. Pooling resources does inherently create some cross-system coupling. By default, you're going to want to reduce coupling across your system. However, there are many cases where this coupling more than pays for itself in terms of massive performance advantage. Like everything else, you just need to look at things case-by-case to see if the trade-offs are worth it.
@xDahl
@xDahl 21 сағат бұрын
@@bryanedds8922 I will happily admit that I don't understand what you're saying here, and that's not a failure on your end to communicate, it's on my end to comprehend.
@paulclarke8237
@paulclarke8237 Ай бұрын
I think the ZII part glosses the case where the program can't just use a dummy value. He's assuming entities in a game that don't get drawn or something, not like rows in a table we need to search.
@justinbasinger7728
@justinbasinger7728 Ай бұрын
I think the idea is kind of like this. Lets say you only page 100 rows at a time. New up the memory for 100 blank row classes at once, don't delete them when you page. Clear them and fill them back up with the new page of info.
@necuz
@necuz Ай бұрын
Why would your search crash or otherwise fail if it encounters a zeroed out struct? Null pointer dereference sure, but you're not going to make it that far on a row that has zero values or is zero length. It's never going to match the search criteria and should pass right through. Or did you mean as a return value? It's pretty easy to draw/print a result that has 0 rows.
@paulclarke8237
@paulclarke8237 Ай бұрын
@@justinbasinger7728 Sure, I can see reusing the same rows, dynamically expanding the table, or even just crashing when we run out of space, but it seemed like he was suggesting something else.
@justinbasinger7728
@justinbasinger7728 Ай бұрын
@@paulclarke8237 I really think it's that simple. Grab all the memory you need up front and deal with orchestrating that chunk yourself, instead of newing/deleteing for every object. I think the N+2 part is being clever with that management, and only getting what you need, trying to be as efficient as possible with it.
@RickeyBowers
@RickeyBowers Ай бұрын
It can work like a sentinel to remove special cases from any loop. The search always succeeds and stays in bounds without bounds checking.
@fromjavatohaskell909
@fromjavatohaskell909 5 күн бұрын
Agree - pools might be extremely effective. I with Java had some ability to create pools (thread heaps?) for objects. For example it might have been very helpful for processing requests in HTTP servers.
@renat1786
@renat1786 Ай бұрын
Thank you, great video. I would just put my 5 cents, i believe there are use case for both n and n+1 approaches, similar way that in CPUs there are one-element (ordinary) instructions and at the same time SIMD instructions. And both instruction sets are valuable and useful. In other words there are tasks about complicated logic about individual entities, and there are tasks where performance is crucial and we must do bulk allocation/calculations.
@Muskar2
@Muskar2 Ай бұрын
I principally agree that there is often some use-case for all kinds of approaches. But even in those rare exceptions where you can't do anything better than a complicated management of individual objects, it's usually still vital to not take on opaque complexity. Thinking about batches is inherently simpler, and allows you to only think about the actual logic of the program, rather than memory management.
@frechjo
@frechjo 7 күн бұрын
Why do people think memory arenas are something new, or associate them with some person in particular? The first time I heard about something like that, was in the Abuse game engine, released in 1997. It's basically a Lisp interpreter, but instead of doing GC, it just clears all non global variables after each frame (or something like that, I don't remember the exact details). There's also a similar pattern in Forth, in which you save the current pointer to the dictionary, create temporary definitions, and reset the pointer back to that point when you are done (I don't know for how long it's been in use, but Forth is pretty old).
@collinkemper681
@collinkemper681 Ай бұрын
"Frankly, I find this clip to have been the second most important advice on programming I ever saw." OK now you have to tell us the first most important advice on programming you've ever seen.
@xDahl
@xDahl Ай бұрын
I have some contenders, but I’m still not sure which one was the most influential, it’s been a while after all.
@oraz.
@oraz. Ай бұрын
If anyone has a written article lmk. I can't learn from videos very well. Sounds like he's just talking about pool allocating being better than raii.
@xDahl
@xDahl Ай бұрын
I can't send you links, as youtube appears to delete my own comments, but google "ginger bill memory allocation strategies", Bill has a few articles on different custom allocators that are well written and simple. Code examples are given in C, so it's fairly easy to follow as no advanced language features are used.
@oraz.
@oraz. Ай бұрын
@@xDahl ok
@bryanedds8922
@bryanedds8922 22 сағат бұрын
here for the bedhead
@edgyzero6305
@edgyzero6305 17 күн бұрын
I can see this type of thinking being a god send in C where keeping track of many small allocations is quite difficult, however in Rust it really doesn't seem to be an issue whatsoever after adjusting to the initial friction of the borrow checker. Of course this style of allocation is still useful for large applications that need large chunks of memory to be reused frequently across different parts of the application but for average use cases of I/O when you already have the borrow checker to handle the "little" pieces the extra overhead of implementing a large allocator seems unnecessary. As much as I love performance and efficiency I think it's good to have the application in mind, if implementing a more sophisticated architecture will net negligible gains only noticeable by nanoseconds then it seems more reasonable to opt for a simpler design. My main point is that I find what he is saying to be useful but not for all use cases and applications as is insinuated.
@blarghblargh
@blarghblargh 15 күн бұрын
performance is not the only benefit from an architecture that bundles all allocations together. he talks extensively about aggregating failure points. rust doesn't get that benefit from the borrow checker.
@edgyzero6305
@edgyzero6305 14 күн бұрын
@@blarghblargh I certainly don't mean to be dismissive of this architecture but I do think I'd need to see it in action to really get it. To me currently the borrow checker really doesn't seem to cause much issues, at least on small scales. I know he mentions thinking about applications at small scales as being the difference between the average programmer and a pro but it does seem like the small approach is valid for small apps. Certainly when making a game the overhead/sys calls for memory greatly increase for thinking about things at a small scale.
@trejohnson7677
@trejohnson7677 29 күн бұрын
n+2 analogy is a++
@lajos76nagy
@lajos76nagy Ай бұрын
Arena allocation is nice ... when you can get it. You can't always get it though.
@Muskar2
@Muskar2 Ай бұрын
What's your point?
@xDahl
@xDahl 28 күн бұрын
Arena's aren't the only custom allocator you can use though, there's plenty of strategies that can be employed.
@yonas6832
@yonas6832 Ай бұрын
i think he would enjoy Zig
@xDahl
@xDahl Ай бұрын
Casey is friends with Jonathan Blow, and it seems like Casey is more interested in JBlow's language, Jai.
@julkiewicz
@julkiewicz Ай бұрын
Isn't that a problem with programming languages? Everything including "struct" in C is designed around the idea of one small thing. I'm yet to see vectorization / tight loop / "groups" support as a first-class idea in a programming language. Even JAI does nothing for that as far as I can tell. We've silently moved from CPUs that do one thing at a time to CPUs that even on a single thread in a single cycle can do long long series of things. Yet our tools are still stuck in the "one thing at a time" paradigm.
@hhvkzk
@hhvkzk Ай бұрын
Look into array languages, like APL, J, BQN, UIUA They are explicitly designed for batch computing.
@joseduarte9823
@joseduarte9823 Ай бұрын
In Julia there's an extra dot you can put in a lot of operations to make them vectorized, even your own functions if written with proper care
@Muskar2
@Muskar2 Ай бұрын
HLSL is a language designed to make it impossible to not make something vectorized, but it has its limits. And as far as I understand, languages are slightly limited by the instruction sets by making it more seamless. Tim Sweeney coined the term 'Vector Complete" in an old article about LRBni (Dobb), which essentially proposed standardizing instructions like vgather, vscatter, masking and setting destination registers, thus making it trivial for compilers to turn most work into SIMD, so long as iterations don't depend on each other and the call graph is statically known. Plus it would make it easier to make a high-level CPU language that learned into vectors. AVX introduced gather, but scatter is only supported on AVX-512 (12% of gamers today according to the Steam hardware survey) and is not implemented very efficiently yet. How do you propose a language could encourage SIMD?
@rabbyte_dan
@rabbyte_dan Ай бұрын
Thanks for sharing this - valuable information indeed! But ads every 2 minutes, seriously? :D
@xDahl
@xDahl Ай бұрын
I am unfortunately not in control over how many ads are shown, this channel and video aren't even monetized, so it's just youtube being youtube.
@quangtube
@quangtube 23 күн бұрын
when people talk about generalized issue, you talk about specific issue :). generic memory pool allocator like dlmalloc was there..what is interesting is since when we start to move back into micro optimization ( vs assembly -> c++ -> java ). the tech and the tool was there just to please the business.
@llamasarus1
@llamasarus1 20 күн бұрын
So, is he saying that pooling instead of frequently creating and freeing is a concept of advanced thinking in programming? I'm not a professional programmer, but use MonoGame and C# in my free time, and even I understand that. Of course, all this programming in a managed environment is probably making me soft, but I still consider how the GC will act with the code I write, even if there's no legitimate performance concern with the scope of my project.
@u9vata
@u9vata Ай бұрын
Its pretty interesting that I do like RAII still - albeit do it for group-based thinking stuff. My little toy language has a lot about RAII to be honest. But very few malloc-free and not "objects" that are "alone" or element-like and ownership should be trivial in a nice cose in 99% of the cases indeed. This is why I totally stopped to fancy rust: They literally solve the wrong problem via a lot of compiler masturbation... But its cool to be able to use RAII for example on adding some stuff for event listeners where the "stuff" going out of scope makes no one possibly ever forget to unsubscribe. I also prefer the scoping + raii way to do locks and mutexes and such. Its just less error prone - albeit I prefer lock-free and spinlock often, but some stuff just does not need that... So I often wonder: Is it n+0.5 or n+2 at this point? I see the value in RAII despite I understand what you say about stage n here... Of course exceptions are awful too... I literally removed any remains of them from my toy lang and feel bad I once wanted to "at least enable" them. Its awful - but I stopped using these like 5+ years ago when I own the codebase (when not owning, its often a need, but you know what I mean).
@Muskar2
@Muskar2 Ай бұрын
At that point, it seems to me you're just abusing that RAII is the easiest thing available in your language. RAII was specifically designed for dealing with individual elements. Anyway, you can accomplish the same ease of use with a 'defer' keyword (which executes at scope exit) or even integrating allocators into the language like Odin and Jai did, such that the only thing you can forget is to write the few lines of freeing logic one (or a few) places in your program. As for multi-threading, I found the ideal thing is to minimize contention (e.g. only contend the interlock line/instruction where the threads are dequeuing jobs). When more contention is introduced, I find debugging swiftly become extremely complicated. Scenarios that require contention by definition are just inherently complex, and I don't have much experience with easy ways to handle such scenarios - albeit the more transparent the easier to debug. But yeah, sounds like n+0.9 ish. n+2 is about taking advantage of what zero means to the CPU, how C can if(struct) to have a uniform data validity check, how all memory is initialized to zero by OS'es by default (for security reasons) [e.g. VirtualAlloc or mmap(MAP_ANONYMOUS)], how you can trivially cast zeroed memory to any size of object without any concern about data corruption or further initialization at the cost of no ASM instructions, and how having an enforced ZII standard in your codebase will make it much less verbose to check for errors and thus a much larger percentage of the code is actually dealing with work, rather than error checking - which makes it easier to refactor, debug and understand.
@u9vata
@u9vata 26 күн бұрын
@@Muskar2 Hmmm.. > At that point, it seems to me you're just abusing that RAII is the easiest thing available in your language. RAII was specifically designed for dealing with individual elements. No. I think it is still good to have language elements that talk about life cycle of things and ownership. When someone fucks the granularity.... well... they would totally suck balls in some language like Zig I think anyways.. > Anyway, you can accomplish the same ease of use with a 'defer' keyword (which executes at scope exit) or even integrating allocators into the language like Odin and Jai did, such that the only thing you can forget is to write the few lines of freeing logic one (or a few) places in your program. No, you cannot do the "same". Because with a well written move semantics you can totally move things out of the scope instead of scope-deleting. You can totally pass around ownership without random extra costs all because of the type system... You can probably say that "you can accomplish the same for measurably many cases" - which is true and useful, but it is sometimes good to pass around the ownership out of local scope. That is with Zig how do you defer in a way to NOT get something deferred to end of scope but to much later? > the only thing you can forget is to write the few lines of freeing logic one (or a few) places in your program. Um... Same can be told about malloc / free too... You can totally just use them naked and "lets not forget it". Also its not just "freeing logic" - but everything that has a life-cycle basically. > Scenarios that require contention by definition are just inherently complex, and I don't have much experience with easy ways to handle such scenarios - albeit the more transparent the easier to debug. Real performant multiprocessing just.... sucks... that's it. There is not really so much magic way around it... There are ways to do it somewhat better (and I spent considerable time doing very inhereintly parallel train control an similar system that are event-based) but I too prefer the approach for single thread -> instruction level parallelism / cache optimized -> either simd or threads approach.... it is surprising how fast a single thread can be when well written and we are not alone on the CPU.... > But yeah, sounds like n+0.9 ish. n+2 is about taking advantage of what zero means to the CPU Yeah n+2 sounds like a very interesting low level coding idea. I did not play around enough with that to say if its really that HUGE or not because you often already "misuse" (use) this property in "normal" code too. But yeah I see validity to study in that direction for sure....
@bruderdasisteinschwerermangel
@bruderdasisteinschwerermangel 13 күн бұрын
I feel like this issue is overblown in general. I still try to convince every programmer I meet that they really don't need memory allocations/the heap at all for 95% of their objects. Yes since I'm looking at this from the embedded world I am very biased. But frankly most things that I have to put on the heap are data containers (string, vector, map, set, ....). These need to dynamically change size so yeah they must go on the heap unless you have a definitive max size. The only other times something goes on the heap with a smart pointer is when it needs to cross thread boundaries. A conrete example was a large data structure (>150 KiB) that is produced in thread A and processed in thread B. I can just call unique_ptr's swap instead of having to copy the data from A's stack to B's stack.
@marcossidoruk8033
@marcossidoruk8033 10 күн бұрын
For strings its useful to use a string builder pattern to avoid unnecessary allocations. For large things such as "vectors" (bad C++ name, they are really arrays) no one is telling you to not use malloc for that when needed. What casey is speaking against is small allocations (individual structs and pieces of data structures) that could have easily been grouped into a single lifetime (the lifetime of the data structure or some program specific lifetime like a frame). You don't need to know the size of a data structure beforehand to group allocations, you allocate a pool of memory of a sufficiently large size and then you assign the memory using an allocation strategy, most often a bump allocator. Wether you agree with this or not, it is an objective fact that this leads to code that is orders of magnitude faster in normal computers than RAII. I have no idea about embedded systems though.
@bruderdasisteinschwerermangel
@bruderdasisteinschwerermangel 9 күн бұрын
@@marcossidoruk8033 Yes a string builder (or std::stringstream in C++'s case) is a fine tool to use. It solves that particular problem rather well. And I also think you should absolutely always use a std::vector over manual malloc calls, solely to ensure you don't leak the memory. A bucket/pool does the same trick and I'm familiar with the pattern and I'm not opposed to it at all. My point is that is solves an issue which can be avoided quite easily. The "small allocations (individual structs and pieces of data structures)" mostly don't need to be on the heap to begin with. Containers that have a runtime determined (or even dynamic) size, yes, that's why we got vector, map and so on. But what else must go on the heap? If its size is known at compile time there's no benefit from putting on the heap aside from certain cases (like the multi threading one I mentioned). I think in a lot of ways, the "buckets" or "pools" or whatever you want to call them, do exist in the code I write, but they're just the containers. I see absolutely no reason for any ol' object (like an entity in your game) to go on the heap *on its own*.
@LinucNerd
@LinucNerd 8 күн бұрын
@@bruderdasisteinschwerermangel If I'm understanding you right, what you're saying is that data of known size and life-time should be put on the call stack whenever possible? (or the global area if appropriate)
@bruderdasisteinschwerermangel
@bruderdasisteinschwerermangel 8 күн бұрын
@@LinucNerd Yeah. And in the case where you might have multiple instances of something, you're probably gonna put them into a container of some sort. It's similar to the bucket/pool allocation to be fair. One other case where this might not work is if you work with inheritance/interfaces. Let's say component A gets a component that implements X and acts as the owner of that object. In that case you need to pass a pointer or reference. I'd probably use unique_ptr (that way A has full ownership of X) or make A non-movable and take the value by raw ptr or reference (but you have to ensure the life times align).
@p39483
@p39483 20 күн бұрын
I've been using realloc and index handles. Struct of arrays are nice for extensibility since new arrays can be added without changing base datatypes. But index code is UGLY while pointer code is clean. I'm thinking allocating blocks of elements that never free and using pointers might be better in some cases.
@VitisCZ
@VitisCZ 28 күн бұрын
But then when you have a team and most of them are in the N phase and then you are in the N+1 how do you deal with that? These aren't really compatible with each other as far as i can understand
@xDahl
@xDahl 28 күн бұрын
There isn't really an easy way to deal with this sort of situation, so I don't know what's the right thing to do. If you're in a team where people have wildly different ways of programming, at best what you can do is share your concerns and ideas with the team; but there's no guarantee that people will be convinced. Even in this comment section, I've had to delete comments from users who are so opposed to any criticisms or alternatives to smart-pointers and RAII, that they immediately went to name-calling and slander; ignoring every argument given by me and others. So the only advice I can give is try to reason with people who are willing to listen, and always be open to the idea that you could be wrong. To some degree you can have subsystems that uses custom allocators and grouped-element-thinking that interacts with N style code, but not always; but I'm not sure if it's a good idea to do that or just accept the N style of programming for the project to keep the codebase consistent.
@kanecassidy9126
@kanecassidy9126 Ай бұрын
wish he gave an example of turning n code into n+1 code. this all sounds too abstract to me without any specific examples
@xDahl
@xDahl Ай бұрын
Yeah, I also struggled to get what he was on about when I first saw this clip, Casey takes a lot of stuff for granted, but try to look into Arena/Bump-Allocators and then Pool-Allocators, generally any custom allocators would do, but these two are simplest and easiest to understand.
@kanecassidy9126
@kanecassidy9126 Ай бұрын
@@xDahl thanks for the hint. I'll check it out
@xDahl
@xDahl Ай бұрын
@@kanecassidy9126 I think youtube deleted my own comment... So I can't send you a link, but google "ginger bill memory allocation strategies". I had forgotten to mention that earlier, I think it will come in handy as Bill gives code examples in C.
@xDahl
@xDahl 29 күн бұрын
​@@kanecassidy9126 Silly me, Casey does give an example of turning N code into N+1 in this video at 18:45, although he doesn't show any code examples. In short, instead of individually allocating every piece of memory that a character needed (like, bones, animations, meshes, maybe textures? etc), he instead wrote a helper function that calculated how much memory he needed for the whole character and its data, and then did one large allocation for the whole thing. And after that, the helper function would set the pointers to reference other parts within the same allocation block. That way, he'd only have to do one allocation rather than 7 allocations, and his error code only had to check if he got the whole block (that can contain everything for that character), rather than checking every single allocation if it failed and handling it.
@kanecassidy9126
@kanecassidy9126 29 күн бұрын
@@xDahl yeah I noticed that part but code example would be nicer. The issue is it's not that easy to turn any individual memory allocation into a batch in practice if there are many independent "moving parts".
@pancio-ciancio
@pancio-ciancio Ай бұрын
Goddam! I'm just starting to think in the way you describe (I like to think at the group as a set, even if it's not technically correct). I was struggling because it seems that only few propose that type of code (at least what I found), and I was thinking I was in the wrong path. I'd like to see how to handle proper object destruction in this Arena allocator
@mileselam641
@mileselam641 9 күн бұрын
Or "How I learned to stop worrying and love coding in Rust". Ownership and lifetimes are not a constant concern when the language supports them out of the box.
@ProXicT
@ProXicT Ай бұрын
After 10 years of programming in C++, this is something I'm slowly gravitating towards. I'm getting sick of the overhead from all the the fancy features the the language wants me to use, but frankly, I don't think C++ is a language suitable for changing this mindset in. If I worked on my own private code base, then I could somewhat imagine it, though it would probably mean avoiding almost everything from the std library, which begs the question if C++ is even worth sticking to. If I wanted others to be able to contribute to my project, this approach is out of the question. I'm seriously considering Zig at the moment.
@xDahl
@xDahl Ай бұрын
I'd just give Zig a try, give yourself a few weeks to play with it and see how you like it. I'd also consider other languages like Jai or Odin.
@frankc2119
@frankc2119 Ай бұрын
@@xDahl add c3 to the list ;)
@Raspredval1337
@Raspredval1337 Ай бұрын
what about generic strings tho? You create them everywhere, the lifetimes are very incoherent. Some automatic memory management would be good here. Unless you make your own redundant string mem_alloc_pool thingy and tie every string to that pool.
@xDahl
@xDahl Ай бұрын
What is a generic string? What's the use case and life cycle in the program? More over, what is the program's lifetime? I'll get to that last one in a bit. Smart pointers are a tool that are fit for a specific purpose, but that doesn't mean they should be used everywhere, and that's unfortunately what a lot of people do. In _most_ cases you can group lifetimes together, and at the same time in many cases, avoid a lot of error handling. You should use specific and well crafted tools to solve for the problems you have, not use generic solutions that "fit everything", because those will not perform well, and will give you other concerns & problems. You can use smart pointers in cases where you _truly_ have arbitrary life times and references, and Casey would probably be fine with that! As for the "program lifetime". I wrote a program a few weeks ago, and I simply used an arena that grows for handling strings. It had an upper bound of 1GB, but it didn't allocate 1GB of memory, just reserves it and grows when needed (Virtual memory is nice). I think it in practice used less than 1MB? Not too sure. And when a reference got stale, it was simply "leaked"; and that's fine! Fact of the matter was that this program would only ever leak a few times with small strings, and it would only run for at _most_ 5 hours. There just wasn't a good reason to care about less than a few pages of memory for a program that isn't perpetually running. Just to clarify (because I've seen people misunderstand this point), I'm not saying you should leak memory willynilly, leaks are bad _in most cases_, but there are times where it's ok to do so. For the record: This is a rare thing for me to do, as in all other cases I've had I don't leak things, I just handle it, and it's easy to do with custom allocators.
@Raspredval1337
@Raspredval1337 Ай бұрын
​@@xDahl so, your solution is to replace malloc with your own fixed-size bump allocator? Or a set of such bump allocators? Idk, coupling coherent things into separated entities is the way to go, but replacing allocators is not something I'd use for generic cases. I'am interested in this because I dabble in prog language design, something C-like, but with nice features, like OOP syntactic sugar, generics and constexpr-like compile-time evaluation. I believe RAII's goal is noble, but the implementation is somewhat lacking. What mechanisms for memory management would you recommend for the general case? Some one size fits all tool like RAII or some set of tools? Letting it sort itself, like in C, proved it to be a disaster
@Mallchad
@Mallchad Ай бұрын
​​@@Raspredval1337yes. this way of allocation applies to all things. and syringe having incoherent lifetimes is entirely problem with the way the programmer writes the code. it is very easy to change the lifetime of an object. "C's problem" is not actually a problem with C itself but a lack of usable libraries to back out that don't encourage slow and bug prone operations. Which C++ mostly fixed. Why I say that is because strings are null terminated. strings have no predetermined size. every allocation is a syscall. no built in bounds checking, no null pointer check, constantly relying on allocations from the system to not fail or pagefault you. No dynamic array library. no allocator. All of these things make C code horrible for safety and performance and stability because the default state is the bad one...
@JaconSamsta
@JaconSamsta Ай бұрын
I think that's kind of the point of the whole thing. Yes, the lifetime of a single piece of memory can be incoherent when looked at in isolation, but usually it is constrained to a larger context. Is it really going to matter that I free the piece of memory as soon as it isn't needed any more (which is how RAII/manually inserting frees all over the place deals with it), or is it maybe sufficient to free it once I've left that larger context? And if I do need to hold onto that value, I can always copy it out before I free the allocation. That can end up being faster than dealing with the general purpose system allocator that needs to deal with synchronization, fragmentation, etc. And sure, if you end up finding that your objects are **so** dynamic, that they somehow make round trips through your entire program, then you can always fall back on some other resource management scheme. But that doesn't have to be the default.
@xDahl
@xDahl Ай бұрын
@JaconSamsta That was so well articulated, I wish I could pin and double heart your comment.
@julkiewicz
@julkiewicz 27 күн бұрын
RAII is not about initialization though. It has initialization in the name, but it's about resource management, not initialization. So "ZII" and "RAII" aren't even in the same category of concepts.
@cunning_weasel
@cunning_weasel 18 күн бұрын
They're different categories of concepts because ZII is n+2 and RAII is n.
@julkiewicz
@julkiewicz 10 күн бұрын
@@cunning_weasel No, they are in a different category of concepts because they solve different problems entirely. It's not even apples and oranges it's apples and sounds
@cunning_weasel
@cunning_weasel 10 күн бұрын
@@julkiewicz exactly. n+2 vs n.
@Vitorruy1
@Vitorruy1 3 күн бұрын
99% of the time it's just used for allocation tho. So the ciriticism is still valid most of the time.
@d.bahandov
@d.bahandov 22 күн бұрын
What is a stub that Casey is talking about?
@almightysapling
@almightysapling 22 күн бұрын
Right? I need more information because it seems like someone asked the question "okay so what happens when you don't have enough memory" and the solution was, if I understand correctly, to just go "la la la" and write to null and hope the stuff you needed didn't matter.
@jc-aguilar
@jc-aguilar Ай бұрын
Similar ideas: kzfaq.info/get/bejne/rNCkoJCSld7Vcmw.html
@xamidi
@xamidi 25 күн бұрын
It doesn't work to avoid smart pointers and repeated/dynamic re- and deallocations in certain scenarios. I know one because I had this requirement as an issue for almost 8 years in my HPC software - it is huge tree structures (of which all parts are dynamically collectable/insertable to other tree structures) that avoid redundancy for the sake of keeping memory requirements low (like, less than 512 GiB because there is so much data and supercomputing nodes are limited). Not using tree structures would make the algorithms (like tree unification) slow down by factors of like 4x, which is unacceptable. (The trees represent formulas which must be quick to handle even when they have millions of symbols.)
@mennovanlavieren3885
@mennovanlavieren3885 20 күн бұрын
Maybe I'm getting your situation wrong, but a suggestion. If you're memory constraint, then you know how much memory you can allocate for the big tree structure at maximum. If that is the case, then you can just allocate that memory, that is reserved in the design anyway, as a heap of tree nodes. Then you can take nodes from there at will and if you no longer need them you can create an other tree of freed nodes. When you run out of nodes at the end of the heap, you start to consume nodes from the free-tree. This is all based on a design time maximum of simultaneously used tree nodes. The pattern of keeping a free-list of nodes has a name, but I don't know that right now.
@earx23
@earx23 13 күн бұрын
Yes, more efficient. All dynamic memory allocation is expensive. If you can do it all static, it's better. Good luck doing everything static in a complex GUI, though. I was an assembly programmer long ago, and that gave me the ability to write way more performant code than most coders, and still does. However, it just all depends on the problem.. For me, as an assembly programmer, it was hard to understand how dynamic web pages are served. .. And never forget: premature optimization is the root of all evil.
@Vitorruy1
@Vitorruy1 3 күн бұрын
That's boomer advice. The average developer doesn't even know enough optimization to do a premature one. The root of all evil nowadays is people writing code they don't need to solve problems they don't have.
@earx23
@earx23 2 күн бұрын
@@Vitorruy1 very true.
@Alexander_Sannikov
@Alexander_Sannikov Ай бұрын
raii has never been about managing pointers. it's about managing resources. and if you have an array of open file handles, the only way to close them is to iterate over them and close each one in one way or another. now the choice is yours: do you want to do this loop manually every time or do you want your array destructor to do it automatically.
@Raspredval1337
@Raspredval1337 Ай бұрын
but what if you have week references? Now, the computer has to spend time in each scope block to go thru every weak ref and check, is it okay to delete, ooops, no it's not, let's continue. Or to check if somebody has moved the resource from the object, to avoid double free. Plus, every time you want to make your own complex abstraction you'd need to satisfy the Rule of 5 (move\copy constructor, move\copy assign, destructor). And you can't just copypaste code from move\copy constructors into move\copy assign operations, what if the end user is _very special_ and selfassigns your objects? Next, think about errors in constructors, the only way to report an error is to throw from the constructor. Or to have valid and invalid states of the object, baked into the object definition. Now, for every method invocation of that could be invalid object you'd have to check if it's valid or not. Or assume it's always valid and have a ticking bomb in your code, waiting to segfault at runtime. You'd need to think about all that. RAII might be good for the end user, who doesn't have to write that code. But it's terrible for people who have to actually write and maintain that. Plus, why do you need to have an array of file handles, all in one scope, waiting to be deleted? If their lifetime is coherent, tie them into a collection and destroy that collection once. It takes like 15 seconds to write effectively a destructor with a for loop for that collection. And, unless you gonna need like 1000 collections of files, completely incoherent from one another, managing lifetimes of those collections would be trivial.
@Kapendev
@Kapendev Ай бұрын
Or write a function Zoomer.
@Alexander_Sannikov
@Alexander_Sannikov Ай бұрын
@@Raspredval1337 Naturally, don't use weak references where an owning reference suffices. Most RAII implementations don't even have weak references. Throwing from a constructor is a valid concern for languages that want to avoid exceptions. However, returning Optional from a constructor is a perfectly valid solution. It's also perfectly valid to disallow uninitialized RAII objects altogether and instead use Optional again for cases when you can't initialize them on construction. Modern languages are really good at handling optionals with pattern matching. File descriptors is just the most basic example of a managed resource. If you don't like it, think of textures, sockets, threads, etc.
@Raspredval1337
@Raspredval1337 Ай бұрын
@@Alexander_Sannikov I do not say automatic resource management is bad, I said, RAII itself is badly designed and in return promotes bad design
@Alexander_Sannikov
@Alexander_Sannikov Ай бұрын
@@Raspredval1337 I think that the reason why C++ RAII is badly designed is largely because optionals didn't exist when the concept was created, and even today C++ does not have modern tools to unwrap optionals ergonomically. That being said, imo even bad C++ RAII is still better than zig's no RAII at all, when it comes to managing resources.
@tugbars4690
@tugbars4690 Ай бұрын
Awesome
@rccsab
@rccsab Ай бұрын
The only problem I see with the arena style is that we lose the ability to catch buffer overflows. Let's say you allocate an array of entities from your arena pool, then you write a loop to iterate them but you make an off-by-one error. If you had allocated that in the heap, you could turn on address sanitizer and it will catch the bug for you immediately. But if that memory came from an arena, I don't know of any way to even know that was the error to begin with. The code will not fail immediately since the "extra" memory is technically valid, and the unknown behavior you will see won't be helpful to determine where the bug is either. I've had my share of very bad bugs of this kind, and currently I moved away from using arenas for that reason. At least during development I prefer to use malloc/free for this reason alone. If anyone has thoughts on this I'd like to hear.
@xDahl
@xDahl Ай бұрын
I am not sure how you make an off-by-one error with pools, elaborate? With a pool allocator, you return a pointer to a struct within your pool, so you reference each struct via a pointer, you're not dealing with an array directly.
@rccsab
@rccsab Ай бұрын
@@xDahl Hey! Well, you can write a push_array function that would return an array of structs allocated in the pool. That's what I mean, in that case, if you either read or write past the array you will corrupt memory from other things, then the app will behave unexpectedly.
@rccsab
@rccsab Ай бұрын
@@xDahl I suppose we're thinking of different kinds of pools. I believe Casey talks about a "generic" memory pool, where you can allocate all kinds of objects. Maybe I'm wrong, but I think you seem to be thinking of a "specific" pool of objects, e.g. a pool of entities only. I was referring to the former, where you can have, in the same pool, different kinds of objects allocated sequentially.
@xDahl
@xDahl Ай бұрын
Ok I see what you mean, but I'm not entirely convinced about this being a valid issue/concern. See, when you allocate an array of entities, you have to request the size/amount of entities, so you already know how big the array is going to be; given that you know the size, you can just do bounds-checking. And if your language allows for it, you can just use a slice and the bounds-checking is automatic during runtime and compile time. Odin has this, and you can even disable run-time bounds-checking on release builds if you so choose. So I find this concern to be kind of odd. If you don't have access to slices, you could do it manually or see if it's possible to do some tricks on the matter. If I remember correctly, GingerBill wrote a header library for C that gave him slices, tho I am not familiar with it and haven't checked on it. That said, there _might_ be a trick you could use with virtual memory to catch these issues. See, given that memory is divided into pages, you could just bump the Arena until the next page minus the size of the allocation, and return that address, but before the return, mark the next memory page as unwritable; Now, if you get an off-by-one and start accessing the next page, you'll segfault, and you can catch that in a debugger. I wouldn't recommend this, I'm not entirely sure it would even work, as I've never done it. But it could potentially work for debug builds.
@rccsab
@rccsab Ай бұрын
​@@xDahl I can agree this isn't a huge issue. I was just pointing out I had problems with this before. The actual point I was trying to make is that we already have the ability to bypass this issue by allocating things on the heap and using address sanitizer (in C that is). So my point is that we lose this ability when using the arenas. My idea atm is to write all the code using malloc/free during development, and once it's all settled down I can replace it all with memory arenas. But it's true that we can either write checks manually or write our own containers with auto checking, then with discipline we can avoid this issue by never accessing memory manually. Idk, I guess I just dislike the fact that we lose the ability to use address sanitizer.
@tamelo
@tamelo Ай бұрын
Good luck managing a very large code base with a duzen of N+1 engineers.
@xDahl
@xDahl Ай бұрын
No luck needed, a lot of teams already do this
@rndszrvaltas
@rndszrvaltas Ай бұрын
How does this apply to resources that aren't memory of a PC but something you really don't want to use longer than you need (and might not need all the time)?
@Spiderboydk
@Spiderboydk Ай бұрын
It's the same. This could be applied to all kind of allocatable resources.
@julkiewicz
@julkiewicz 27 күн бұрын
It doesn't apply at all. They aren't even the same category of things. How would "zero initialization" help with a typical use case of say file handle management. They only relate in that they both have "initialization" in the name.
@rndszrvaltas
@rndszrvaltas 27 күн бұрын
@@julkiewicz To be a bit more fair, zero initialization was a marginal part of the video. Now here is the thing: this video seems over-concerned with memory and memory only, and even that only in a PC environment. These principles are perfectly applicable with GC as well - allocate a lot and then reuse it and basically never even run the GC at all - where memory is clearly a solved problem. RAII on the other hand never claimed to be only about memory (perhaps that's why it's not called MAII) and in general these other resources (a file handle, a database connection, whatever) are the more open-ended problem and I would naively think that indeed you still have to do basically the opposite for all that stuff.
@Spiderboydk
@Spiderboydk 27 күн бұрын
@@julkiewicz Handling non-memory resources with an arena allocator works just as well as memory resources. You can put file handles into an arena. It just that it would lack certain benefits that memory resources uniquely has, namely having to loop through it to close the handles, but the principle is the same.
@Spiderboydk
@Spiderboydk 26 күн бұрын
@@rndszrvaltas The reason there is so much focus on memory is because absolutely everything you do with a computer revolves around memory. Yes, this is applicable when using a GC too, but it can be surprisingly complicated and sometimes the GC can be surprisingly antagonistic to what you're trying to do. Yes, RAII is about all kinds of resources. Nobody has claimed it's for memory only. Howevery, since memory is such a big deal compared to other resoruces, it warrants finding custom solutions to the memory case of the problem, since custom solutions are usually easier to find and more efficient than general-purpose solutions.
@alexloktionoff6833
@alexloktionoff6833 Ай бұрын
So you mean N+1 programmer is about ARENAs memory management?
@xDahl
@xDahl Ай бұрын
Not quite but close, it's about realizing that individual-element-thinking and individual-element-allocation is a poor way of architecting a program. If you have a bunch of elements that in a wider context share the same life-time, then they should be grouped into the same allocation in an allocator. This is achieved by using custom allocators that fetch a big block of memory and then serve out pieces of memory for that particular context. Doing so removes a lot of error-handling, it removes a lot of bugs, it makes the program more secure, it makes the program faster, and makes the code easier to understand. When you pass around an allocator, you're also explicitly saying something about the elements & data you're working with, you're in a way encoding their life-time and relatedness as a value in code, and you're saying something about their intended usage. No amount of comments can do that, as comments can become out-of-date with what your code actually does, and comments can just be wrong by saying what you meant, not what you did.
@alexloktionoff6833
@alexloktionoff6833 Ай бұрын
@@xDahl ok, so Arena is not the only solution. It could be Arenas, custom allocators or startup-allocation/no-run-time-allocation for mission critical embedded applications. Right?
@xDahl
@xDahl Ай бұрын
Correct, Arena's are not the only way, any custom allocator or allocation strategy that is well-fitted to your code would do. It doesn't just apply for mission critical code or embedded applications, you can use these ideas where applicable.
@bjbboy71697
@bjbboy71697 Ай бұрын
What editor is this?
@susheyfish
@susheyfish Ай бұрын
Looks like neovim, but could also be emacs
@salim444
@salim444 Ай бұрын
4coder but it really doesn't matter
@UnidimensionalPropheticCatgirl
@UnidimensionalPropheticCatgirl Ай бұрын
it’s 4code, which is kinda like microemacs
@luminousmonkey4512
@luminousmonkey4512 Ай бұрын
Like @salim444 says, it really doesn't matter.
@xDahl
@xDahl Ай бұрын
4coder is similar to microemacs?
@error17_
@error17_ Ай бұрын
Did he finish the handmade hero series? Last upload was a year. Anyone know?
@jupiterapollo4985
@jupiterapollo4985 Ай бұрын
No, It seems like it's pretty much dead. No new updates and he doesn't seem to talk about it at all...
@xDahl
@xDahl Ай бұрын
afaik, he just got busy with other things. Though he also says that he isn't good at game design, gameplay that is, so once you get past some stage of the development cycle, things get less interesting.
@jupiterapollo4985
@jupiterapollo4985 Ай бұрын
@@xDahl The whole point of the handmade hero project was to create a "complete game, from scratch" within roughy 2 years. It didn't need to be some super interesting grand game and he didin't need to be that good at game design like a Jonnathan blow or something. It was specifically about the programming steps to making a game, and It should have stayed that way. But somewhere along the lines he started doing too much such as adding 3d sprites and doing more complex things with the world, and it seemed to overwhelm him? If he just kept it simple it probably would have been done by now...
@KennethBoneth
@KennethBoneth Ай бұрын
in a sense he did finish it, but then started adding 3d and lighting to the engine after that and no longer has time for it.
@dave7244
@dave7244 25 күн бұрын
@@jupiterapollo4985 These guys who spend a lot of time talking about their ideas don't seem to finish their games promptly.
@a2sbestos768
@a2sbestos768 26 күн бұрын
Grouping is good. ZII sounds like a step back, tbh. A bit like interpreted languages / javascript in particular - everything "works" and doesn't crash, but you can't for the love of it figure where the error resides because there's was undefined somewhere, and you didn't check. Absolute worst.
@xDahl
@xDahl 25 күн бұрын
In ZII you make the zero value useful and the default. It's not "undefined" to get or use a zero value, it's expected and the default behavior because you defined zero to be usable.
@LeutnantJoker
@LeutnantJoker 8 күн бұрын
​@xDahl that is still not correct behavior in all cases. I agree with the OP. Crash is often better than "just works" because just works might not be correct and can make things a debugging hell. Null stubs have their place but calling them higher order thinking I'd ridiculous and they're not a better form of RAII, they are a totally different concept. And I still don't understand why ges talking about RAII in the context of allocations. Raii is about resource management, not about memory allocations. Yes sometimes memory can be thr resource, but most times it's something else, like a database connection or file handle or drawing context or whatever. None of this made any sense. Yes having a h8ghef level view of data and lifetime is useful. But that has nothing to do with error handling or RAII those are dll complete separate problems.
@xDahl
@xDahl 8 күн бұрын
@LeutnantJoker I was talking about ZII, meaning you make the default/initial value zero, and having that be either useful or not an error. I don't see the connection you're making with ZII vs crashing early. These aren't mutually exclusive. Edit: I think I see the confusion, so let me clear that up. In ZII, you make the zero value useful / nor an error, an example of this would be that variables are always initialized to zero, and you write your code in such a way that zero is expected and the default. (Yes you can have some cases where you need to initialize a variable to something other than zero, bla bla bla, you can always have exceptions, the idea behind ZII is to _try_ to make zero the default, not that is _always has to_) Or say you have a struct with a bunch of variables, if you write your code in such a way that zero is the initial value and that's expected, then you don't have to do any initialization code, because the OS already zeros out memory for you when you request memory. Yes I'm aware that Malloc doesn't necessarily zero out memory before reusing old chunks, but you should be using custom allocators anyway. In practice, zeroing out memory is practically free, the OS already does it before serving out memory to you for security reasons. Only place you have to do so yourself manually is in your custom allocator, when reusing old memory. So what about crashing early? This seems to have caused some confusion. So lets say you have a procedure that returns a pointer to a struct. In ZII, all of the bytes within the struct will be zero, as to keep all variables zero when initialized. And since your code expects and treats zero correctly, there's no issue, cost or problems. But lets say you cannot allocate a struct from your custom allocator - you can still crash from this if you so please. See, there's nothing contradictory with ZII and crashing early, you can do both. If not being able to get a struct is an unrecoverable state in your program, then sure, crash... But you can still treat zero as the initialization value... AKA, ZII: Zero Is Initialization. I hope this helps clearing up the confusion.
@LeutnantJoker
@LeutnantJoker 5 күн бұрын
@@xDahl I know what Zii is, thank you. My point stands
@xDahl
@xDahl 4 күн бұрын
@@LeutnantJoker Well, so does mine. We were talking about ZII and crashing early, and as I stated, they're not mutually exclusive. Am I to assume the issue you're having is not with ZII itself but with stubs?
@beaverbuoy3011
@beaverbuoy3011 Ай бұрын
Epic
@simonfarre4907
@simonfarre4907 Ай бұрын
He is right. Until he isn't. And he is right all the time in this video. Problem is, some domains don't lend themselves at *all* to thinking about collections. Events can't be grouped for instance. IPC message passing? Nope. The list can be made very long. What's so great about game dev is that literally _everything_ can be thought of as groups of things. Literally everything in the application from the small to the large. Every single thing. But literally every software domain that doesn't involve physics simulations, can't.
@MarekKnapek
@MarekKnapek Ай бұрын
C++ analogy: Use std::vector instead of std::list, vector is better because it has size + capacity, thus much fewer number of allocations. Or even better, try to push back 20 elements: In case of vector use range-based insert or reserve, it will do only single allocation (don't use reserve in a loop tho).
@Veeq7
@Veeq7 Ай бұрын
That's the first level. The more accurate annalogy would be using a monotonic buffer resource for all vectors with shared lifetime (for example parsing stage, json document, render pass, or game tick)
@peacefuldragon1831
@peacefuldragon1831 Ай бұрын
use array when possible, and use compile time initialization when possible
@saniancreations
@saniancreations Ай бұрын
Not a good analogy. Vectors are usually still small (and many) parts in your program, if you have multiple vectors then you have to worry about freeing all of them and you are right back to stage N. Preferably you want to have your vector make use of a custom allocator such as a pool. But to make vectors use a custom allocator you have to write a bunch of bothersome template shenanigans. I tend to write my own dynamic array classes (strike that, structs) that work better with my own allocators, it's really not that hard, and it's nice to not need stdlib in your projects because the whole thing is written in the stage N mindset.
@pierreollivier1
@pierreollivier1 Ай бұрын
@@saniancreations That's why I use Zig, in Zig custom allocators are first class citizens, every data structures in the std expect an allocator, and they usually provide a Managed/Unmanaged variant in case you don't want your data structure to hold a useless reference to your allocator, they also provide a lot of different allocators and making them is quite easy. The neat thing is that because they have so many allocators, you can swap them based on the situation, for example during compilation if I'm in a debug build I'll use comptime to see if I'm in a debug build and instead of an arena or an heap allocator, I will pass a debug allocator, to prevent double free, use after free, and memory leaks with the General Purpose allocator, but when I compile in Release I use comptime to switch to a more suitable allocator.
@OmarGindia
@OmarGindia Ай бұрын
He is writing c++
@MorningNapalm
@MorningNapalm Ай бұрын
Smart pointers are really orthogonal to memory fragmented by many small allocations. You can still lose track of memory allocated from a block. I would also caution that games programming, especially small team games programming, is a very special field, where performance is so important that you drop as much as possible to gain a few cycles. In large systems things are very different. There is so much code, and such large teams, that you need to adopt a whole different approach for things to work smoothly, and architecture, APIs, safety and contracts become much more important. Horses for courses.
@xDahl
@xDahl Ай бұрын
You can lose track of memory allocated from a block depending on your custom allocator, so it's not a given. And losing track of a piece of memory in a block can be fine depending on the circumstances. It really is a shame that Casey can talk about how these things _don't_ _just_ improve performance, but increases security, code readability and maintainability, generally simplifies the program, makes bugs less likely and can remove entire classes of bugs and error-handling - and a lot of people just go "games programming is different, this doesn't apply to _anything_ outside of games, and performance isn't everything". Also "drop as much as possible to gain a few cycles"; he just doesn't use unnecessary tools, unnecessarily large libraries and inappropriate methods to get a job done. You can do this in any field of programming, not introducing unneeded complexity is a good thing.
@MorningNapalm
@MorningNapalm Ай бұрын
@@xDahl I agree that keeping things minimal in general is a very good thing, and I do this myself, but so much of what Casey says is simply his opinion or relevant to his situation. Try to tell a bank not to use smart pointers, and see what happens to their software quality. Listening to Casey is a lot like listening to Jonathan Blow, both very intelligent, and both make a lot of unconventional decision, which happen to work _in their situation_, and then preach as if it is always applicable, which it is not. It is pointless to discuss these kinds of things in KZfaq comments, this isn't the right place.
@MorningNapalm
@MorningNapalm Ай бұрын
@@xDahl By the way, I debugged a common out of memory problem we were having in a major software renderer used by Hollywood in movies like Matrix 2 & 3, Constantine, Cat Woman, etc., and discovered that we had plenty of available memory, but it was all horribly fragmented. I instituted a flushing BSP, wrote the reader-writer locks for Windows, Linux and Irix, and added a block allocation memory subsystem, and after that, we could render much larger scenes again, with almost no thrashing, so I am very familiar with the kinds of technologies that Casey talks about, but he just talks as if his arguments are always valid, but in fact every system and every situation has different constraints, and game programming is off on the far edge of what is needed, as is computer graphics in general. There are many kinds of systems where totally different solutions are needed. Think space, medical devices, distributed systems, international banks, military, and so on. All have completely different constraints than the one-guy game programming studio.
@xDahl
@xDahl Ай бұрын
"but so much of what Casey says is simply his opinion or relevant to his situation [..] and both make a lot of unconventional decision, which happen to work _in their situation_"... It's a shame that the points I brought up in my initial reply has been entirely ignored, in favor of still going "games programming is different, this doesn't apply anywhere else". Also, "... happen to work in their situation...", happen to work? What are you on about? None of what's being said here is limited to only his situation, this isn't just a game dev thing, people have been doing these things for decades outside of games. Look, when you pass around an allocator, you're also explicitly saying something about the data you're working with, you're encoding their lifetime and relatedness as a value in code, and you're saying something about their intended usage. "Happens to work"? This isn't some random system they came up with that happens to work, this is an extremely valuable tool that's been around for decades that replaces the need for smart-pointers in a lot of cases. Does that mean all? No, but that was never the argument. If you truly have some data with an undetermined life-time where the relatedness of things is hard to pin down, yeah you can use smart-pointers, but that is a specific case, and even there you don't have to. Generalized tools like smart-pointers bring with them a lot assumptions and problems that could in _many_ cases be avoided if you just structured things differently. You should use the tools that are appropriate for the task, instead a lot of people just slap smart-pointers over a poorly architecture program and call it a day, that's what Casey is arguing against.
@MorningNapalm
@MorningNapalm Ай бұрын
@@xDahl I am not arguing for slapping smart pointers in places they don't belong, but Casey, Jon and others often talk as if what they say is universally relevant, sometimes quite explicitly so, and it just gets my goat. "You should use the tools that are appropriate for the task" - fully agree, let's leave it at that.
@sofiaknyazeva
@sofiaknyazeva Ай бұрын
What about std::string (from C++ STL) and std::string::String (from Rust std)? These are probably the most used smart pointers in a typical C++ and/or Rust codebase. And you don't have full control over their allocation/de-allocation strategies.
@julkiewicz
@julkiewicz Ай бұрын
You can try to use fixed-size strings instead (much like a database would) or have a collection of all strings that is more centralized and then use ids to reference them instead.
@JaconSamsta
@JaconSamsta Ай бұрын
What about them?
@sofiaknyazeva
@sofiaknyazeva Ай бұрын
@@julkiewicz It's unclear to me what you mean by "a fixed size string". Do you mean, string_view (in C++) and str (in Rust, which size must be known at compile time) or do you mean constants? This isn't _always_ possible, due to the lifetime issues and a general use-case of that string. For instance, in Rust, you must allocate memory for string literals, if the string size isn't known at compile time, same goes with C++. "A collection of all strings", sorry, but this is a terrible idea. It might be useful for trivial programs, but when something complex as (for example Chromium), it's just not possible to centralize everything or portions of everything. HashMap (if we not create a custom data structure) is close to what you've explained, but in a meaningful way, they can't be centralized. Also, a lot of std functions, returns a string or a collection of string (usually in a vector). For that, you can't do much, because you still have to use those anyway, so you can't just abandon the idea of allocating each smart pointers individually. A better solution might be creating a string pool, storing everything in there, and accessing them as we go along, but this idea is complex already, which requires doing some possible hacks with smart pointer and not worthwhile for trivial programs.
@jeyko666
@jeyko666 Ай бұрын
Just use smallstring-style container. Stack allocated, but if it goes above max size, it goes to heap. Of course you can substitute heap with an allocator.
@curtis6919
@curtis6919 Ай бұрын
@@sofiaknyazeva How would the size of a string literal _not_ be known at compile time? This makes zero sense. There are _zero_ cases where a string _must_ be dynamically allocated; people just do it because it's what's taught and in some cases easier to reason about. But it's absolutely not necessary.
@AChannelINeed
@AChannelINeed 22 күн бұрын
A common complaint from newcomers to Odin is the lack of constructor / destructor / optionals and gingerBill always reply they are useless and that Odin instead enforce an "everything is zero" by design instead. He's at N+2 stage while newcomers aren't.
@siyabongampongwana990
@siyabongampongwana990 Ай бұрын
What is a n+1 programmer?
@TonyDiCroce
@TonyDiCroce Ай бұрын
Now I'm sure xDahl will just say I'm trapped in N thinking BUT the way I think about this is a little different. For me the lifetime of a thing should be related to its necessity within the program... e.g. this is why I think the stack is the natural best place for almost everything... Stack frames come into existence when a thread executes them and are cleaned up when the thread goes somewhere else. Things like malloc()/free() and unique_ptr are for the exceptions to this (which probably shouldn't be huge in number). So in a way I see groups as well, but my groups are a little less abstract (because they are tied to realities in the program: the stack, the heap)... That's not to say I don't see a place for arena allocators... Of course they make sense. I even agree that things that have the same lifetime should be 1 allocation (this is why I like C++'s default object composition strategy (e.g. objects are really composed unless you do composition through a pointer (and therefore a separate allocation)).
@Muskar2
@Muskar2 Ай бұрын
The stack is effectively the same thing as the simplest linear allocator, but with some limitations. You can implement your own stack allocator in very few lines of code, but it sounds to me like you're almost at n+1. You only need to realize that the stack and heap are abstractions that are rarely useful. They all use the same memory. The stack is effectively pre-allocated memory for your application that you're pushing and popping data to and from. A simple allocator works basically 1:1 with that and instead of writing my_struct Data = {}; you write my_struct Data = PushStruct(Arena, my_struct); where PushStruct is a macro: #define PushStruct(Arena, type) (type *)PushSize(Arena, sizeof(type)) and PushSize is a function: static void *PushSize(memory_arena *Arena, u64 Size) As for your last point, it seems like the missing realization of n+1 is that delaying allocation and freeing of individual objects to the exact moment that they need to be used/discarded is extremely complicated and wasteful. It is much simpler to simply allocate a large allocation at once, and grow that memory if necessary, and then dispose of it all once that entire lifecycle is done. Having concerns about whether individual elements are no longer needed (or not needed until a few lines later) is unnecessary busywork, and you might also need to change the order of those mallocs/frees a lot when refactoring or adapting to new functionality, which again is unnecessary busywork.
@danilomenoli
@danilomenoli 7 күн бұрын
I really don't like this Casey Muratory guy. There are much better, humbler and better teacher programmers out there you can listen to.
@xDahl
@xDahl 7 күн бұрын
Fair if you don't like him. Keep in mind that this is an excerpt from a stream of his, you can check out his videos on his channel where he presents himself a fair bit differently. But whatever floats your boat :)
@feliciafasterthanlight5796
@feliciafasterthanlight5796 14 күн бұрын
Did he really say any memory from the OS is zero by default unless you are on an embedded system? Oh have seen that error made before! That's how you end up with code that runs on every computer BUT the one you want to run it on.
@xDahl
@xDahl 14 күн бұрын
Correct me if I'm wrong, but memory from the OS is indeed zeroed-out by default. Linux, FreeBSD, Mac and Windows does this for security reasons. However Malloc (the allocator) doesn't necessarily do it before reusing old memory that it fetched from the OS, but that's not the OS fault, the OS gave malloc zeroed-out memory.
@deeplow4492
@deeplow4492 7 күн бұрын
@@xDahl when malloc used mmap it will get zeroed out pages from the OS, but for small allocations it uses sbrk and arenas which just refuses the same memory region without zeroing it out
@xDahl
@xDahl 3 күн бұрын
Oh I see what's meant here. See, malloc is a general purpose allocator from the standard C library, but malloc isn't the OS, so you cannot depend on malloc always giving you zero'ed out memory, but what Casey is advocating for is using custom allocators instead of malloc. When your custom allocator asks for new memory, it is zero'ed out by the OS and you don't have to do anything, and any time you need to reuse old parts, your custom allocator just zeros it out first. The cost of doing so is negligible considering the OS already does it for fresh memory. Unless you have truly unknown lifetimes and relations between allocations, you should be using custom allocators instead of malloc, thus you can rely no memory being zero when allocated. Malloc being a generalized allocator means it has to be _very_ inflexible in the assumptions it makes, thus it is also a poor fit for a lot of situations.
@jacekjacenty
@jacekjacenty Ай бұрын
Are you reinventing garbage collection, but in a different form? Instead of one global garbage pool, do you introduce many local teaspoons of garbage?
@sunkittsui7957
@sunkittsui7957 Ай бұрын
I think that way that Casey uses and garbage collection differs is that garbage collectors usually need to perform some form of checks when freeing memory, but you don't need any checks when clearing an arena and can just do it at the end of it's lifetime.
@saniancreations
@saniancreations Ай бұрын
No. Here's the differences: With garbage collection you don't think about allocating and freeing memory, and it lets you think of everything in your program as small individual objects. But a garbage collector does some non-trivial analysis on your program to keep track of when you are no longer using each individual object, which is slow. Without garbage collection you DO have to think about allocating and freeing, so if you continue to think of everything as small objects, you now have to perform the job of the garbage collector, making sure each individual object is freed when it is no longer used. This is faster than letting a separate system do it because you have better knowledge of the structure of your program, but so many objects are hard to keep track of, so it's easy to make mistakes, and it is also still not optimal. What Casey is suggesting is this: You still think about memory allocation, it is not automatic, but you think of it in large blocks and model them after the tasks your program has to perform. Not local teaspoons, large chunks. This allows you to 1) not worry about managing every tiny object, and 2) make the memory management of your program much much faster. It is basically preferable to the above in every sense.
@sunkittsui7957
@sunkittsui7957 Ай бұрын
@@saniancreations perfectly said
@Muskar2
@Muskar2 Ай бұрын
Building on @saniancreations great reply, it is like allocating memory for the entire tea party once, and then you can just do whatever at the party, including changing it up midway, and once the party is over, you just have one line that frees the entire party, instead of having to manage all the elements. And if you have a party every weekend, then you don't even truly free it to the OS, you simply zero all the data in that party/pool once every party is over, and reuse it. And for smaller short-lived things, you can simple use a linear allocator where you simply keep pushing new things on it when you need something, and then every once in a while, you erase everything on it. E.g. once per frame, or a similar predictable end of a main loop iteration. It makes it about as easy to work with as a GC'ed language is, without paying the costs of GC.
@angrymurloc7626
@angrymurloc7626 Ай бұрын
I learned MVC architecture in middle school, I don't know what this guy is on about with the n+1 being unteachable
@xDahl
@xDahl Ай бұрын
I don't think that was his uncertainty, more that he isn't sure if it's possible to skip the N+0 stage all together and instead jump straight to N+1. The N+0 stage is after-all such a basic way of thinking, an comes very natural to everything that preceded it.
@paulclarke8237
@paulclarke8237 Ай бұрын
mvc doesn't have anything to do with this?
@angrymurloc7626
@angrymurloc7626 Ай бұрын
@@paulclarke8237 separating data model from the logic is exactly what he is describing there, except he uses the analogy of grouped vs ungrouped data, which might but not has to always be the most efficient data model
@frydac
@frydac Ай бұрын
@@angrymurloc7626 MVC doesn't necessarily imply you allocate/deallocate in larger collections all at once, your model can just be an OOP class hierarchy with lots of small heap allocated objects that get created/destroyed gradually as described by Casey.
@angrymurloc7626
@angrymurloc7626 Ай бұрын
@@frydac and if that is a sensible data model for your program then the bug frequency is gonna be low ( even though I don't believe in OOP in general, and a data model having method inheritance baked into it is really silly since you're never supposed to use it)
@tacticalassaultanteater9678
@tacticalassaultanteater9678 29 күн бұрын
A giant pile of nontrivial constraints you have to remember to all uphold manually isn't good architecture, it's the steep price you pay for good performance, and no big brain architecture will remove the price so it should only be paid once you are sure you need the performance.
@xDahl
@xDahl 28 күн бұрын
What Casey is advocating for is a lot easier than the stuff you do at stage N, custom allocators don't just give you a performance boost, it greatly simplifies your code, removes a lot or error-handling, makes your code less buggy, makes your code more secure etc. Grouping elements together means you have less mental overhead, and passing custom allocators around tells you a lot about how the data is going to be used and its lifetime, you're encoding intent, lifetime and relatedness as a value in code; that is greatly beneficial. People don't just do this for "performance", there's a lot of benefits to structuring your code this way.
@thewelder3538
@thewelder3538 Ай бұрын
Wow, do you talk a lot of complete bollocks.
@xDahl
@xDahl Ай бұрын
Wow, I'm telling your mom!
@thewelder3538
@thewelder3538 Ай бұрын
@@xDahl She's been dead for a long time, so I'm not sure that's going to yield the response you'd hoped for. However, if she had still been around though, she probably would have chastised me for my choice of language. However, I've found in my 35+yrs of coding, people that use terms like RAII, SFINAE and others, usually write pretty shit code. Now you may very well be an exception to the rule, but I doubt it.
@BestGameDesigner
@BestGameDesigner Ай бұрын
@@thewelder3538 1) The guy in the video is Casey Muratori. @xDahl just clipped one of his videos. 2) Did you even watch the video? Casey is literally arguing against RAII.
@thewelder3538
@thewelder3538 Ай бұрын
@@BestGameDesigner I've really got no idea what he was actually trying to communicate in the word salad anyway. To be fair, I've not heard of him and I've been coding games at a well known software house for like the last 15yrs. Moreover, if the video was clipped from John Carmack or Peter Molyneux, I'd still have the same opinion. I know what RAII is, do I care about it? No. Never even crosses my mind. About the only discernable meaning I got from watching this was it's a good idea to allocate and prepare as many resources as possible in one hit to remove as many possible error conditions you have to check for. To be fair, I'd have thought any coder worth their salt would be doing this anyway. However, the reality is that it's not always practicable to do, as then you end up passing around a load of stuff. Anyway, it's just my opinion on what I sat through.
@eduantech
@eduantech Ай бұрын
So what are you agnry at? I don't get it.
@almightysapling
@almightysapling 22 күн бұрын
TL;DW: call reserve() before emplace_/push_back() ;)
The Only Unbreakable Law
53:25
Molly Rocket
Рет қаралды 325 М.
Пройди игру и получи 5 чупа-чупсов (2024)
00:49
Екатерина Ковалева
Рет қаралды 3,6 МЛН
Мы сделали гигантские сухарики!  #большаяеда
00:44
Logo Matching Challenge with Alfredo Larin Family! 👍
00:36
BigSchool
Рет қаралды 22 МЛН
天使救了路飞!#天使#小丑#路飞#家庭
00:35
家庭搞笑日记
Рет қаралды 89 МЛН
Linus Torvalds: Speaks on Hype and the Future of AI
9:02
SavvyNik
Рет қаралды 170 М.
Why i think C++ is better than rust
32:48
ThePrimeTime
Рет қаралды 296 М.
How much faster has Mojo's dictionary gotten?
7:40
EKB PhD
Рет қаралды 3,4 М.
Interview with Jonathan Blow at LambdaConf 2024
26:34
LambdaConf
Рет қаралды 20 М.
"Papers I Have Loved" by Casey Muratori
1:08:04
PapersWeLove
Рет қаралды 76 М.
Rust's Alien Data Types 👽 Box, Rc, Arc
11:54
Code to the Moon
Рет қаралды 142 М.
Java Is Better Than Rust
42:14
ThePrimeTime
Рет қаралды 242 М.
i wrote my own memory allocator in C to prove a point
5:23
Low Level Learning
Рет қаралды 363 М.
Пройди игру и получи 5 чупа-чупсов (2024)
00:49
Екатерина Ковалева
Рет қаралды 3,6 МЛН