Speaker 1: Hey, everyone, ready for another deep dive Today. We're going
Speaker 1: to look at assembly language.
Speaker 2: Ooh, assembly language going low level.
Speaker 1: Yeah, but you know, assembly language can seem a little
Speaker 1: bit intimidating, a little bit like a you know, a
Speaker 1: relic of the past for a lot of programmers. Yeah,
Speaker 1: but I think that understanding it, even if you're not
Speaker 1: writing it directly, can make you a much much better
Speaker 1: programmer in any language. What do you think?
Speaker 2: I completely agree. I think it's kind of like knowing
Speaker 2: how your car works. You know, you don't need to
Speaker 2: be a mechanic to drive, right, right, Yeah, but if
Speaker 2: you know how your car works, you're probably going to
Speaker 2: be a better driver.
Speaker 1: Right. You'll know when something's wrong, Yeah, exactly. Yeah, you'll
Speaker 1: be able to maybe fix it yourself, or you'll know
Speaker 1: when to take it to the shop.
Speaker 2: Absolutely.
Speaker 1: So to guide us on this journey today, we got
Speaker 1: some excerpts from the book Thinking Low Level Writing High
Speaker 1: Level Write Great Code. And this book it's really interesting.
Speaker 1: It really busts some myths about compiled and about optimization,
Speaker 1: and it gives you some really actionable insights that I
Speaker 1: think can level up your coding game.
Speaker 2: Yeah. I think one of the mythsetbusts is that compilers
Speaker 2: are so good these days that there's really no point
Speaker 2: in learning assembly language, right, which I think this book
Speaker 2: kind of proves that that's not true.
Speaker 1: So why why should we be skeptical of let's say,
Speaker 1: like compiler benchmarks, right, Like, we see benchmarks all the time.
Speaker 1: This compiler's faster, this one's more efficient.
Speaker 2: Well, you got to think about who is making those benchmarks.
Speaker 2: Usually it's the compiler vendors themselves, so they're going to
Speaker 2: write code that makes their compiler look good.
Speaker 1: So it's like if you were, you know, designing a
Speaker 1: racetrack for your specific car. Yeah, exactly, it might not
Speaker 1: be a fair comparison to other cars. Absolutely, so the
Speaker 1: results might not reflect like real world performance.
Speaker 2: Yeah totally.
Speaker 1: And that's where I think assembly language can really shine
Speaker 1: because it can help you. You know, if you can
Speaker 1: understand how your code is running at that machine level,
Speaker 1: you can make spot Bottle likes yourself. Yeah, you can
Speaker 1: write more efficient code even if the compiler is not
Speaker 1: doing everything perfectly for you. So it's about developing that
Speaker 1: low level intuition. Absolutely, even if we never write assembly
Speaker 1: code ourselves, we could still.
Speaker 2: Benefit from that totally, and you realize pretty quickly that
Speaker 2: even a simple statement that you write in a high
Speaker 2: level language, like just a simple calculation, can actually end
Speaker 2: up being multiple machine instructions when it gets translated down
Speaker 2: to assembly. For example, there's this example in the book,
Speaker 2: a visual Beasic statement. It says profits is sales cost
Speaker 2: or goods overhead commissions.
Speaker 1: Yeah, I mean that's basic math. You'd think that would
Speaker 1: be one instruction, one instruction.
Speaker 2: But at the machine level, at least on the eighty
Speaker 2: by eighty six architecture, the CPU can only work with
Speaker 2: like two operads at a time, so you're going to
Speaker 2: have to have multiple subtract instructions in assembly to actually
Speaker 2: carry that out.
Speaker 1: So even though it looks really simple in our high
Speaker 1: level code, it can be a whole process.
Speaker 2: It can be a whole dancer team, yeah, for the CPO.
Speaker 1: Yeah yeah. So, speaking of the eighty by eighty six architecture,
Speaker 1: what are some maybe some mind blowing things that you've
Speaker 1: learned about eighty by eighty six.
Speaker 2: One thing that might surprise people is that those familiar
Speaker 2: eighty by eighty six registers like EAX, EBX, ECX.
Speaker 1: Right, the general purpose registers.
Speaker 2: Yeah, those aren't actually all separate entities.
Speaker 1: I always imagine them as like separate little like, you know,
Speaker 1: little boxes, little boxes in the CPU.
Speaker 2: Yeah, yeah, but they a lot of them overlap. So
Speaker 2: the lower sixteen bits of EAX, that thirty two bit
Speaker 2: register that can be accessed as a sixteen bit register
Speaker 2: called AX.
Speaker 1: Oh.
Speaker 2: Interesting, and then AX that's divided up into two eight
Speaker 2: bit registers ah and al.
Speaker 1: So it's like those registers are like a Russian nesting doll. Yes, yeah,
Speaker 1: you know, registers within registers exactly. You got to be
Speaker 1: mindful I guess of how changing one register might affect
Speaker 1: other ones.
Speaker 2: Yeah.
Speaker 1: Yeah, it's a historical design decision they made to maintain
Speaker 1: backward compatibility, right, right, But it's really interesting. It's something
Speaker 1: you don't think about when you're writing high level code. Yeah.
Speaker 1: What other kind of low level secrets does this book reveal?
Speaker 2: Another one is index addressing mode, okay, which is how
Speaker 2: you access array elements. Oh yeah, and how that works
Speaker 2: can really influence how you structure your code.
Speaker 1: So can you give us a quick rundown of what
Speaker 1: indexed addressing mode is?
Speaker 2: Imagine you have an array of numbers stored in memory, right,
Speaker 2: so each element of that array is going to be
Speaker 2: at a specific address in memory. Indexed addressing lets you
Speaker 2: calculate the address of a specific element by adding an
Speaker 2: offset to the base address of that array.
Speaker 1: So like, if I want to access the fifth element
Speaker 1: of an array, I add an offset of four.
Speaker 2: Yeah, if it's zero based indexing, exactly, you've got four
Speaker 2: to the beginning on its address. Yeah, and that gets
Speaker 2: you to the fifth element.
Speaker 1: Wow, it's amazing how much is happening under the hood
Speaker 1: that we don't really think about at the high level.
Speaker 1: So we've talked about how compilers. You know, they handle
Speaker 1: a lot for us, but they're not perfect. Yeah. What
Speaker 1: are some things that compilers struggle with, especially when it
Speaker 1: comes to optimization.
Speaker 2: Well, one of the things they struggle with is they
Speaker 2: only have a limited amount of time to analyze and
Speaker 2: optimize your code.
Speaker 1: Right, It's like a time constraint.
Speaker 2: Yeah, exactly. The process of optimizing code can get incredibly complex. Yeah,
Speaker 2: so they have to use all these different techniques and
Speaker 2: they're often limited by time.
Speaker 1: It's like if you were trying to solve like a
Speaker 1: giant Sidoku puzzle, but you only had a certain amount
Speaker 1: of time to do it. Exactly, you might not be
Speaker 1: able to find the most optimal solution.
Speaker 2: Yeah, exactly.
Speaker 1: So how do compilers, I guess, like, how do they
Speaker 1: approach this problem?
Speaker 2: They use a few different techniques. One of the biggest
Speaker 2: ones is data flow analysis, where they track how data
Speaker 2: moves through your code. And they also use basic blocks,
Speaker 2: which is where they kind of break down your code
Speaker 2: into a smaller, more manageable chunk.
Speaker 1: So the basic blocks are like building blocks for optimization.
Speaker 2: Yeah, you could think about it that way. Yeah, it
Speaker 2: makes it easier to analyze and transform the code.
Speaker 1: Yeah, that's fascinating. I mean, it's incredible to see how
Speaker 1: much it's happening behind the scenes, even with something that
Speaker 1: seems as simple as compiling code.
Speaker 2: Oh, it's a whole world down there.
Speaker 1: This has been a fantastic you know, first part of
Speaker 1: our journey into thinking low level writing high level. We've
Speaker 1: learned a lot already about this hidden world beneath our code,
Speaker 1: and there's so much more to come, and we'll pick
Speaker 1: it up next time. Let's stick with us. Welcome back
Speaker 1: to our deep dive. We're continuing our exploration of thinking
Speaker 1: low level writing high level.
Speaker 2: Yeah, we're really seeing how these low level concepts can
Speaker 2: make us better high level coders.
Speaker 1: Absolutely. Last time, we talked about how even simple calculations,
Speaker 1: you know, they could be way more complex at that machine.
Speaker 2: Level, right, and how compilers they do a lot of
Speaker 2: the heavy lifting for us, but they're not always perfect.
Speaker 1: Yeah, So let's dig a little deeper into some of
Speaker 1: those techniques that compilers use to optimize code, and you
Speaker 1: know how we can actually write code that kind of
Speaker 1: plays nicely with those techniques. Yes, make sera drop easier,
Speaker 1: their job easier, exactly. So one of the most fundamental
Speaker 1: optimizations is constant folding. You know about constant folding.
Speaker 2: Oh yeah, constant folding. It's a classic, super simple but
Speaker 2: really powerful.
Speaker 1: Yeah, so just remind me how does it work.
Speaker 2: So let's say you have an expression in your code,
Speaker 2: like you know, five plus three. A compiler that's doing
Speaker 2: constant folding, it'll actually evaluate that expression at compile time
Speaker 2: and just replace it with the result eight in the
Speaker 2: final code.
Speaker 1: So it's like pre calculating all the easy stuff so
Speaker 1: the CPU doesn't have to waste time.
Speaker 2: Yeah, exactly. It's low hanging fruit. Yeah, but it can
Speaker 2: make a real difference, especially if you're doing that calculation
Speaker 2: over and.
Speaker 1: Over again, right in a loop or something exactly very cool.
Speaker 1: So what other tricks do compilers have up their sleeves.
Speaker 2: Well, a close relative of constant folding is constant propagation.
Speaker 1: Okay.
Speaker 2: This is where the compiler basically sees that you've assigned
Speaker 2: a constant value to a variable, okay, and then it
Speaker 2: just substitutes that value directly into expressions that use that variable. So,
Speaker 2: for example, you might have pi equals three point one
Speaker 2: four nine, and then later on you have circumference to
Speaker 2: pie radius. Right, the compiler can just go ahead and
Speaker 2: replace that pie with its.
Speaker 1: Actual value throughout the code.
Speaker 2: Throughout the code. Yeah, you don't even need to, you know,
Speaker 2: fetch the value of pie from memory every time. It
Speaker 2: saves a little.
Speaker 1: Bit of time, saves a little bit of processing power. Yeah.
Speaker 1: So I'm starting to see how understanding these optimizations can
Speaker 1: make us more like efficient coders ourselves.
Speaker 2: Absolutely, it's like you're speaking the compiler's language, helping it out.
Speaker 1: So what other optimization techniques should we be aware of?
Speaker 2: So another important one is dead code elimination. Okay, it's
Speaker 2: pretty much what it sounds like, getting rid of dead code. Yeah,
Speaker 2: any code that will never be executed like spring cleaning.
Speaker 2: Yeah exactly, our program, so you might have like a
Speaker 2: conditional statement, right.
Speaker 1: Like an Eiffles block, one of those branches. Maybe it
Speaker 1: can never be.
Speaker 2: True, right, so it's never going to execute.
Speaker 1: It's just never going to happen. So the compiler can
Speaker 1: just go ahead and get rid of that code entirely.
Speaker 2: That's great. It makes our program smaller, potentially faster.
Speaker 1: What else, there's common sub expression elimination. So this is
Speaker 1: where the compiler will see that you're doing the same
Speaker 1: calculation multiple times with the same inputs okay, and it'll
Speaker 1: just do that calculation once okay, and then use the
Speaker 1: result everywhere.
Speaker 2: So if I'm calculating like the area of a circle
Speaker 2: over and over again with the same radius, yeah, yeah exactly,
Speaker 2: it's like it can figure that out.
Speaker 1: Yeah. It's basically like factoring out a common factor in
Speaker 1: an equation. It simplifies things.
Speaker 2: These optimizations are fascinating. I never really thought about, you know,
Speaker 2: how much is going on behind the scenes, isn't it. So.
Speaker 2: One more that I think it's really interesting is loop
Speaker 2: invariant code motion.
Speaker 1: What is that? So you have a loop, right.
Speaker 2: And there's some code inside that loop that maybe it
Speaker 2: doesn't actually need to be inside the loop.
Speaker 1: Okay, so it's not changing every iteration.
Speaker 2: Exactly, it's loop invariant. It doesn't change, right. The compiler
Speaker 2: can actually take that code and move it outside the loop.
Speaker 1: Okay, so it only gets executed once.
Speaker 2: Yeah, exactly, and that can save you a bunch of time,
Speaker 2: especially if it's a loop that's running a lot of times.
Speaker 1: So these optimizations, it's like we've been driving with parking
Speaker 1: brake on all this time.
Speaker 2: Yeah, a little bit.
Speaker 1: Yeah, and now we're learning how to take it off
Speaker 1: and let our code really fly. So we've seen some
Speaker 1: of these compiler techniques, but what can we do as
Speaker 1: programmers to kind of write code that's more optimization friendly.
Speaker 2: Well, one of the first things you got to understand
Speaker 2: is operator precedence.
Speaker 1: Ah, the order of operation.
Speaker 2: Yeah, pem das right. We all learn that in school.
Speaker 2: But it turns out compilers have to follow those same
Speaker 2: rules too.
Speaker 1: So if our code isn't clear about the order of operations.
Speaker 2: Yeah, it can get confused.
Speaker 1: It can best things up.
Speaker 2: Yeah, you can get the wrong results.
Speaker 1: So another important thing to consider is side effects. What
Speaker 1: are side effects?
Speaker 2: A side effect is basically when a statement in your
Speaker 2: code does something other than just calculating a value. Ok
Speaker 2: So it might change the value of a global variable
Speaker 2: or write to a file.
Speaker 1: Right, It's like a hidden action happening in.
Speaker 2: The background, exactly. And this can make it really hard
Speaker 2: for compilers to optimize code because they have to consider
Speaker 2: the order in which those side effects might happen.
Speaker 1: So it's like trying to I don't know, choreograph a
Speaker 1: dance where some of the dancers are doing their own
Speaker 1: thing off stage.
Speaker 2: Yeah, that's a good analogy.
Speaker 1: So minimizing side effects and making them really explicit can
Speaker 1: help the compiler do its job better.
Speaker 2: Yeah, make its life easier.
Speaker 1: So we've got operator presidents, we've got side effects. What
Speaker 1: other tips can you give us, Well, let's.
Speaker 2: Talk about control structures like is statements and loops and
Speaker 2: switch statements, right.
Speaker 1: The things that control the flow of our code.
Speaker 2: Exactly. The book talks about how to write those control
Speaker 2: structures in a way that lets the compiler implement them efficiently.
Speaker 2: For example, with Eiffel statements, Right, you can often figure
Speaker 2: out which branch is more likely to be taken, Okay,
Speaker 2: and if you structure your code so that more likely
Speaker 2: branch is kind of the default path that can help
Speaker 2: the compiler generate more effect It's like planning.
Speaker 1: A route where you're probably going to take the highway
Speaker 1: instead the back roads. Yeah, exactly, you know, make it,
Speaker 1: make it the most efficient path.
Speaker 2: The book also talks about switch statements, which can be
Speaker 2: optimized using things like jump tables, which are really interesting.
Speaker 1: Some tables. What's a jump table?
Speaker 2: So imagine like a table that maps each possible case
Speaker 2: value in the switch statement to the corresponding block of code. Okay,
Speaker 2: so instead of like evaluating a bunch of conditions, it
Speaker 2: can just jumped right to the right place.
Speaker 1: So it's like a super efficient lookup table for code execution.
Speaker 2: Yeah, you got it.
Speaker 1: Okay. What about loops? Loops? You know they're so essential,
Speaker 1: but they can also be a big performance bottleneck. Oh yeah,
Speaker 1: for sure if we're not careful. Yeah.
Speaker 2: The book talks about how to avoid writing code that's
Speaker 2: going to get in the compiler's way when it's trying
Speaker 2: to optimize loops. So things like minimizing the amount of
Speaker 2: work you're doing inside the loop right and using loop
Speaker 2: counters effectively. It's about making that loop really predictable.
Speaker 1: This is like streamlining the assembly line exact to make
Speaker 1: it as smooth as possible.
Speaker 2: You got it.
Speaker 1: Wow, this is incredible. It's amazing to see all these
Speaker 1: optimizations that are happening that most of us probably don't
Speaker 1: even think about.
Speaker 2: Yeah, it's a whole world down there.
Speaker 1: So this has been a really insightful look into the
Speaker 1: world of compiler optimization.
Speaker 2: It's amazing what they can do, isn't it.
Speaker 1: And how you know, we as programmers can actually help
Speaker 1: them out a.
Speaker 2: Little bit, give them a hand exactly.
Speaker 1: But we're not done yet. There's still more to explore
Speaker 1: in thinking low level writing high level. Stick with us
Speaker 1: for the final part of our deep dive. Welcome back.
Speaker 1: It's the final part of our deep dive into thinking
Speaker 1: low level writing high level.
Speaker 2: We've covered a lot of ground, haven't we.
Speaker 1: Yeah, it's been a real journey exploring how those low
Speaker 1: level assembly concepts, you know, they can inform the way
Speaker 1: we write high level code.
Speaker 2: Yeah, it's all about building that intuition.
Speaker 1: Yeah, Like understanding those fundamental principles, even if we're not
Speaker 1: writing assembly code every.
Speaker 2: Day, right, makes you a better programmer overall, no matter
Speaker 2: what language you're using.
Speaker 1: Absolutely, So for this last part, I want to focus
Speaker 1: on a specific data structure, one that we use all
Speaker 1: the time. Strings.
Speaker 2: Strings. Ah, yes, those simple sequences of characters.
Speaker 1: Yeah, but as we'll see, you know, there's more to
Speaker 1: them than meets the eye.
Speaker 2: There's a lot going on under the hood. Different ways
Speaker 2: to represent them in memory.
Speaker 1: Right, and they can have a big impact on performance
Speaker 1: and memory usage.
Speaker 2: Absolutely. So let's talk about some of those different ways
Speaker 2: to represent strings.
Speaker 1: Yeah, refresh my memory. What are some of the common types.
Speaker 2: Well, the one you probably see most often is the
Speaker 2: zero terminated string.
Speaker 1: Oh yeah, the null terminator.
Speaker 2: Yeah. Classic. Yeah. It's basically just an array of characters, right,
Speaker 2: but at the very end there's a special character, a
Speaker 2: null character represented as zero and no. Yeah, that's it,
Speaker 2: and that signals the end of the string.
Speaker 1: Simple effect it is, But.
Speaker 2: It has one little quirk. Since that null character marks
Speaker 2: the end, you can't have any null characters within the
Speaker 2: string itself.
Speaker 1: Oh, I see. That could be a problem if you're
Speaker 1: trying to store certain types.
Speaker 2: Of data, yeah, like binary data or texts that might
Speaker 2: contain nulls. For that, you might need a different type
Speaker 2: of string.
Speaker 1: Okay, so what's another option.
Speaker 2: Another one is the length prefixed.
Speaker 1: String length prefix Okay.
Speaker 2: Instead of relying on a terminator, it stores the length
Speaker 2: of the string explicitly, usually as an integer right at
Speaker 2: the beginning.
Speaker 1: So it's like the string is carrying a little size.
Speaker 2: Tag exactly tells you exactly how many characters there are,
Speaker 2: so you.
Speaker 1: Can have any character you want within the string itself.
Speaker 2: Yeah, null characters, no problem. The downside is you need
Speaker 2: a little extra space to store that length information.
Speaker 1: Uh. Trade offs, y, always, trade offs always.
Speaker 2: The book also talks about HLA strings, which are specific
Speaker 2: to the HLA language.
Speaker 1: High Level Assembly HLA strings.
Speaker 2: And they kind of combine elements of zero terminated and
Speaker 2: length prefix a hybrid approach. Yeah, you could say that.
Speaker 1: Interesting. Now there's another type of string that I've always
Speaker 1: been a little fuzzy on. Descripture based strings. Ah.
Speaker 2: Yes, descriptor based strings.
Speaker 1: What are those?
Speaker 2: So instead of storing the string data directly, they use
Speaker 2: a separate data structure called a descriptor. Okay, and that
Speaker 2: descriptor it points to the actual string data, right, but
Speaker 2: it also contains information about the string.
Speaker 1: So it's like a filefolder that tells you about the strength.
Speaker 2: Yeah, good analogy. It gives you the length the character
Speaker 2: said all that stuff. The advantage is flexibility, right, you
Speaker 2: can share string data more easily. Okay, but there's an
Speaker 2: extra layer of indirection there, so it can be a
Speaker 2: bit slower.
Speaker 1: I see. So each type of string has its pros
Speaker 1: and cons.
Speaker 2: Yeah, depends on what you're trying to do.
Speaker 1: Now, beyond these basic types, the book mentions some more
Speaker 1: specialized string representations, like reference counted strings.
Speaker 2: Ah. Yes, reference counting, that's a clever one.
Speaker 1: That's a memory management technique, right.
Speaker 2: It is, each string keeps track of how many references
Speaker 2: to it exist, like how many pointers are pointing to it.
Speaker 2: When that count goes to zero, nobody's using it. The
Speaker 2: memory gets.
Speaker 1: Freed, so it's like a self cleaning system exactly.
Speaker 2: Helps prevent memory leaks.
Speaker 1: Okay, and then there's the whole world of Unicode strings Unicode,
Speaker 1: but it also introduces some challenges, right.
Speaker 2: Yeah, Unicode can represent characters from any writing system, which
Speaker 2: is amazing, right, But those characters, they might take up
Speaker 2: more space than a simple ASKI character, right.
Speaker 1: Because Unicode has to handle a much larger set of characters.
Speaker 2: Yeah, exactly, So string operations might take a little longer.
Speaker 1: So it's something to keep in mind.
Speaker 2: Definitely, especially if performance is critical.
Speaker 1: This has been a really eye opening look into strings.
Speaker 1: You know, I use them every day, but I never
Speaker 1: really thought about the different ways they can be represented.
Speaker 2: It's a good reminder that even those basic building blocks
Speaker 2: have their own complexities.
Speaker 1: Absolutely. Now, before we wrap up, I want to touch
Speaker 1: on something else. The book talks about something that really
Speaker 1: surprised me. What's that The impact of file organization on
Speaker 1: code efficiency.
Speaker 2: File organization, it might not seem obvious, but it can
Speaker 2: make a difference.
Speaker 1: Yeah, I never thought about it. So how does that work?
Speaker 2: So operating systems they manage memory in chunks, right, okay,
Speaker 2: And if you organize your code, well, you can make
Speaker 2: it easier for the operating system to load and manage
Speaker 2: those chunks.
Speaker 1: I see. So it's like if you're packing a suitcase,
Speaker 1: you want to put all the heavy stuff together.
Speaker 2: Yeah, kind of like that, so.
Speaker 1: You're not constantly digging around for things exactly.
Speaker 2: The book talks about things like locality of reference.
Speaker 1: Locality of reference Okay.
Speaker 2: It means if your program accesses one memory location, it's
Speaker 2: probably going to access nearby locations soon after.
Speaker 1: So keep related things together, keep.
Speaker 2: Things close, makes everything run smoother.
Speaker 1: Fascinating. Well, I think we've just about reached the end
Speaker 1: of our deep dive. It's been quite a journey, it
Speaker 1: really has. We've explored so much about how those low
Speaker 1: level concepts they can inform and empower our high level.
Speaker 2: Coding, even if we never read a line of assembly ourselves.
Speaker 1: Exactly, And you know, it's not about becoming assembly language experts.
Speaker 1: It's about developing that low level intouition, that ability to
Speaker 1: see how things really work under the.
Speaker 2: Hood and write better code as a result, exactly.
Speaker 1: So keep exploring, keep learning, and keep pushing the boundaries
Speaker 1: of what's possible with code.
Speaker 2: Happy coding everyone, until next time.