Australian sticker price includes 10% GST so the correct way to compare is to take the tax free US price, convert that to AUD and add 10%. Applying that process yields $1633 AUD so the sticker price we get is actually better than the US price.
From my experience Git LFS is extremely brittle and will break your local check out if you so much as breath on it wrong. Perforce is an expensive solution to the problems of LFS, and I've yet to find a workflow as powerful as my git workflows for managing code.
I don't really see what the article is talking about as the future, but if P4 or Git LFS is the best we can do as a species then we're doomed. All VCS options suck for one reason or another, I hope we don't stop trying to make something better. If only to save me from perforce.
> Git and P4 are not meant to be directly competitive here.
True, but for the problems that Git LFS solves P4 is usually the other option. P4 handles large binaries very well, caveat being the rest of P4. In some projects the caveats are worth it. I work in video games and our in house engine has an unfiltered checkout size over a 1TB.
I've found LFS very brittle when bouncing between branches and moving around in history. There's been many times I've had my local check out blow up through rebases or mistakes I've made. Many times the recovery is to nuke and re-clone. With a 1TB repo that's not a good option.
It's been a while since I've used it, and I'm sure some of my problems were skill issues, but I never found it worth the pain at any scale I've tried to use it.
I'm not convinced the performance benefits are entirely the result of the more compact object representation. It definitely would help, but looking at the code snippets the author provides for the add instruction there's an important structural change that would be making a huge difference.
The old, enum based value type used a single big match statement to dispatch between all possible type combinations. Their assembler output looks like the match gets compiled to something like a big stack of nested if statements.
The new code uses an explicit fast path check with a dispatch into a tagged 'cold' path when the common case isn't hit. The generated code is a single upfront branch for the fast path that exits immediately, with a dispatch into the slow path in a separate function.
This would be contributing significantly to the performance improvements. The old path requires taking several branches even on the hot path. The new code has a single, highly predictable branch that skips all the messy dispatch for the other types.
This could have been implemented for the enum based value type, and I would expect to see a jump in performance there too even without the new compact value type. There will be a much higher branch predictor hit rate with the explicit fast path.
Author here. The disassembly for the old enum handling had many spills, simply because the old value enum can't fit in a single register. If you have an instruction that two operands with two of those big value enums, it needs 4 registers instead of 2. That, coupled with better cache-friendliness, explains a lot.
Absolutely, I agree. I suspect that explicit branch for the hot path is doing a lot too.
Separating the hot path into a prefix before calling into a separate cold function should still generate better code. Your prefix only needs to allocate registers and stack space for just that single path. You would only pay the spilling costs in the old code off the hot path rather than every instruction. And I would expect the branch prediction accuracy of that prefix check to be higher than having the hot and cold paths all dispatching through the same tree of branches.
However it's speculation until you measure so I could be wrong.
Good article in any case, I enjoyed reading along.
100% agreed (see my sibling comment), this all accumulates for in-language function calls,etc since code at runtime often spends a surprising amount of time just moving around values instead of doing useful work, having them just as singular register values really helps a ton.
This was probably 15-20 years ago at this point but I was doing a lot of micro-tests in terms of dynlang->C/C++ transpilers (in relation to my MsC thesis) to see the effects of different value-models, GC strategies and the win of compiled code vs interpretation/JIT
(Some might say that a transpiler via C skews the result but it did more or less optionally generated code without delving into writing a good lowlevel codegen).
Don't remember exact results but you could clearly see the stages that made up the raw computation performance differential (12x at the time iirc) between CPython and V8(JS).
Each of the above steps made up for a 2x-4x differential (don't remember the ratios exactly but combined about 12x or the V8 / CPython differential).
- Interpretation vs JIT (surprisingly a smaller than expected benefit)
- Memory model, moving from naive referece counting (very frequent per-operation bookkeeping operations) vs GC (GC does work, but compared with a GC doing small/incremental work it's miniscule compared to a naive ref-counter)
- Moving to tagged primitive integers from a "fat" tagged type (ie tag+ptr/value), biggest surprise to me, a bit like in this article.
Like the article mentions in the end, you have twice the number of values to move around, with singular values they just ride along in registers but also the fatter representation will make it far harder for the compiler to manage register allocations, remember a dynamic runtime doesn't only move around values, there's often GC or other context objects being kept around that contribute to register pressure.
On top of that, I don't remember the exact author, probably referenced in the old 90s Agesen type inference papers, but a very high percentage of operations in compiler code is just related to moving around values (think function arguments,etc), every instance of those becomes moving around 2 values instead of just a single register.
Tl;Dr; If it's not your first rodeo in compilers, IMHO just design your runtime primarly for register-passable values from day one, it might feel like premature optimization, but since the value model will permeate so much of the runtime, the knock-on effects once you do decide to fix it probably makes it worth to go with it from day 1.
Strongly agree. Ironwood (https://github.com/ironwood-lang/ironwood) takes that to the limit by throwing away the JVM :) The static type is the tag, so nothing is tagged at runtime. Primitives are machine-width values, references are one pointer, and generics specialize in the closed world, so the language has no boxing at all.
> If it's not your first rodeo in compilers, IMHO just design your runtime primarly for register-passable values from day one, it might feel like premature optimization, but since the value model will permeate so much of the runtime, the knock-on effects once you do decide to fix it probably makes it worth to go with it from day 1.
Day one, definitely. Those were cheap to fix only because the value model never had to change.
Umm, Java/JVM has mostly static typing (if you discount the generic mess) but regardless Java/JVM computations has no type-tags for values (vtables for object types however).
This discussion is mostly in relation to dynamically typed languages such as JS (Not Java), Python,etc.
And unless the language was initially designed for static typing it's not always applicable to mix in discussions of static typing (I literally wrote my thesis on the subject of type inference).
Since when do Samsung ship driver updates? I'm being a bit hyperbolic but from my experience Samsung don't ship them very often either. To Google's credit, the Pixels get them quite frequently. Samsung is still better than most other Android vendors, who are somehow even worse.
It's depressing how bad the drivers are. They have 0 market pressure to improve the quality, unlike PC where the whiniest group on the internet (gamers on reddit) will crucify you for driver problems. I've spent too much time investigating and implementing workarounds. Though it does pay my bills...
Since you own a S series device, and it gets shipped alongside OS updates.
Other Samsung devices aren't so lucky.
PC drivers are equally as bad when using laptops by the way.
While you can try to use the plain drivers from Intel, AMD and NVidia, usually the ones in the laptops have custom changes made by the OEM for the laptop motherboard design, and you can get into issues like thermal handling, when not using the drivers provided by the OEM update services.
The Pixel 10 and 11 GPUs are offensively bad. Not only are the Power VR GPUs slow, and less capable feature wise than Mali and Adreno (Xclipse is still around too!), but the tile Google put in the Pixel 11 is actually an _older_ design than what they put in the Pixel 10. They just clocked it higher to make up the performance difference.
And then Google has the gall to charge basically flagship prices for their under powered junk.
Personally I don't buy flagship phones because I don't use their full compute capabilities anyway, but a Pixel 11 would cost 2x what I paid for my Pixel 8. For 0 GPU performance improvement.
I have fond memories from back when Pixels were actually flagship. The Pixel 2 is my nostalgic pinnacle. I'd totally forgotten about that squeeze gesture until I got my old Pixel 2 out to erase.
It's not really obvious unless you go in depth of the details on modern GPU architecture. GPUs aren't really SIMD, they're SIMT (single instruction multiple thread). The silicon looks a lot like SIMD, but the programming model is different.
If you go look at AMD's ISA docs (they're public) you'll see you don't have the equivalent of a __mm256 register like on x86. Each 'thread' just deals with single scalar values like int32 of float32. The hardware, however, groups 32 or 64 threads together which all run the same program and runs them together. Each 'thread' loosely maps to a SIMD lane. The SIMD is implicit, not explicit.
The main difference is that the 'SIMD' execution is somewhat opaque to the program. You just write plain scalar code and the hardware model dispatches it efficiently to SIMD execution units. It's not really an abstraction because to extract maximum performance you have to understand how it works. You can use this kind of programming model on a CPU too, Intel did it with [0] ISPC. It's a C-like language that has execution semantics similar to GPU shader languages but compiles to regular CPU code, and maps threads to your CPUs SIMD lanes like a GPU.
Yes, of course writing naive code assuming each lane in a thread group is a real thread is going to cause problems, but I didn't feel like I needed to go into that level of detail replying to someone just learning about GPU internals. I tried to cover this loosely by mentioning how you need to know how it works for maximum performance.
If you want to get more pedantic you also need to look at your target hardware and their specific micro-architectural quirks and features to get the best performance. AMD specifically benefits a lot from exploiting the scalar unit over the vector unit, you save loads of register file space if you can keep data in SGPRs over VGPRs. There's lots of traps you can fall into where you can load data from buffers into SGPRs but they get promoted to VGPRs because the scalar unit lacks an opcode for like one math operation you did to the value somewhere.
While each lane isn't truly a thread because it doesn't have its own PC the programming model definitely tries to make it seem that way. The threads can terminate at different points too. And again, the ISA isn't a vector ISA. Your register values are scalar.
> While each lane isn't truly a thread because it doesn't have its own PC the programming model definitely tries to make it seem that way. The threads can terminate at different points too. And again, the ISA isn't a vector ISA. Your register values are scalar.
This is not correct. If you check AMD's documentation there are explicit mentions of vector registers (VGPR), vector ALUs, and vector instructions. The introduction to Chapter 2 describes it as a vector ISA.
> RDNA4 shader programs (kernels) are programs executed by the shader processor. Conceptually, the shader program is executed independently on every work-item, but in reality the processor groups up to 32 or 64 work-items into a wave, that executes the shader program on all 32 or 64 work-items in one pass ("wave32" or
"wave64").
A VGPR is not the same thing as a vector register like in SSE4 or AVX. Each addressed register contains a single 32-bit value. A VGPR differs from an SGPR in that each thread in a thread group can have a different value in that register. An SGPR will have a uniform value shared with all threads in a group.
An add instruction on an AMD GPU adds two scalar values. If they're in a VGPR then each thread will add two values unique to that thread. A SIMD ISA as is common on a CPU is different because an add instruction explicitly adds a vector of values. xmm1 stores 128-bits of data. VGPR[1] stores 32-bits of data vectored over 32-64 threads in a thread group.
Without special instructions a thread can't access the VGPR values stored in other threads.
> False. If they were threads they'd have their own PC. They do not - only the warp has a PC.
They are using the term SIMT as it is normally used[1]. The "single instruction" part means that there is only one PC shared across multiple 'threads'.
Actually not so false anymore. (But still they don't expect you to use this knowledge while coding, and you should treat all threads in a warp as moving in lockstep)
> In GPUs of compute capability 7.0 and later, independent thread scheduling allows full concurrency between threads, regardless of warp. With independent thread scheduling, the GPU maintains execution state per thread, including a program counter and call stack... [1]
This might be more confusing than it needs to be. SIMD and SIMT are not mutually exclusive.
People commonly think of things like vector registers when they talk about SIMD, and each "thread" in a GPU warp definitely deals with local vector registers. Granted, they may be slices of superwide registers shared by the whole warp, or whatever else, but from the programmer's perspective, that's a valid way to think about it.
Put another way, it would be a mistake to think that each lane of a vec4 in a shader gets processed by a separate unit.
Most games haven't rendered directly into the "screen buffer" for 15-20 years.
Vast majority of titles use deferred rendering, and lighting is done off screen too. Usually the only thing done to the "screen buffer" is a final post-process pass or a copy.
Deferred renderers work differently from compositors. They still build up the entire displayed frame in screen space, except they dont write into a color buffer like directly, but produce a bunch or intermediate buffers, called G-buffers. Then they have a postprocess pass when they resolve these buffers into the final image.
This is called 'compositing' but its similar in name only. It's a fairly efficient process where each color pixel is produced by reading these buffer targets and producing a final color in a shader.
This is entirely different from what composited apps do, where they build up the app's background into a texture, and push that onto the screen, with potentially multiple screen's worth of windows living in memory. This would be equivalent in video game terms to rendering every character and object in the level as 'stickers' and then making the final image of these cutouts, which would consume tons of RAM uselessly, and would force us to render crazy amounts of detail that would never get shown.
It's one possible rendering pipeline. Is it really most games?
Regardless, video games normally update the entire screen (or window) every frame, because the screen is so dynamic. This is unlike Microsoft Excel which has a mostly static screen. Building Excel as if it's a video game is going to waste resources.
You can't actually ship a game on the porting toolkit without breaching the license. It's intended by Apple to aid porting to use Mac's native APIs one component at a time, but the license forbids redistribution or commercial use. The community can hack away with it however they want because Apple isn't going to chase every individual person, but a business can't ship it unless they want a nice call with Apple's legal team.
That's largely what matchmaking aims to solve, assuming you have enough players you can get a reasonably balanced match to drop people into.
Bots can teach basics, but many of these games rely on abusing game mechanics in ways the developers didn't think of in development so it's impossible to teach. Or things are physically difficult to pull off.
Movement shooters especially have this problem because a core part of the game is abusing the movement system and abilities to out maneuver other players. You can explain the how, but executing it all automatically is a real skill that requires practice. I adore Titanfall 2. I sunk hundreds of hours into it. It's really something else when you get the movement down, it all happens so fast you can't think about it you just have to do it. There's no way to teach that with a tutorial, it's muscle memory and perseverance.
Titanfall 2 has all of these things, campaign, skirmish with bots and PvE, but the skill floor and ceiling is still brutal. Whether it failed or not is a question of how long you expected the game to last, but it still succumbed to the same fate. The more experienced players pulled the ladder up behind them and the matchmaker didn't have enough players to give everyone a fair match.
reply