Due to the way the first Pentium 3 CPUs (Katmai) were built, they likewise aliased the x87 (and thus, MMX) registers to the XMM SSE registers, but this was hidden from programs. It wasn't until at least the Coppermine revision that they were separate registers again.
It's static text with a few images... that doesn't display until you tell NoScript to enable Javascript from a pile of third-party sites.
My family got a PIII 533Mhz coppermine, in the early sideways Slot 1 configuration. Blazing fast at the time. Been a long time since I heard anyone say "coppermine".
CPU progress was wild in the 90s, where you could wait two years and your new CPU would be double the old one's speed at the same price point. Today it takes near a decade for CPU speed to double.
I also recall the first game that advertised its exciting use of the new MMX technology: POD.[1]
An aside, but when SSE came a long that was a real big leap in 3D performance, just as GPU's started to gain some independence. So in about 2010, I tried to fire up Turok 2 just to see how fast it would run on a then modern CPU/GPU setup. It couldn't crack 200fps, however games only a year or two later would fly way past that. Turok 2 came out just before SSE and thus basically ran in purely x86/x87 space, thus the performance gap.
MMX optimization practically required assembly language. The Pentium MMX was an in-order dual pipe CPU, and while compilers supported MMX intrinsics, their code generation for it was abysmal. Visual C++ 6, for instance, would emit code that was 2/3rds register-to-register moves, with values being unnecessarily moved between two registers between each ALU op. This was also a problem with SSE/SSE2 intrinsics. The worst case I saw was the _mm_set_epi8() intrinsic, which was used to construct a 128-bit vector from 16 inputs. When used with all constants, it should have generated a single 128-bit constant load; instead, Visual Studio 2008 generated ~80 instructions to compute it from byte loads. Microsoft didn't fix it until VS2010.
The latency of MMX instructions combined with the in-order dual pipe architecture also made asm loops messy. Simple ops were single-cycle, but multiplies had 3 cycle latency, stores required data an additional cycle in advance, and computed load/store addresses were also needed a cycle in advance. Simply running 2-4 iterations in parallel wasn't an option as there were only 8 vector registers and you'd still get bottlenecks on functional units. Getting peak performance thus often required interleaving loop iterations with special entry and exit code around the loop.
The issue with EMMS is understated. When the CPU switched to MMX, it marked the entire x87 stack as full. If you forgot the EMMS instruction, it wasn't just some strange floating-point bugs that would happen -- the next few x87 floating point calculations could just outright produce NaNs due to FP stack overflow. Furthermore, as these NaNs propagated, the CPU required microcode assists to handle them. So, even if the program didn't crash, an entire calculation domain would get poisoned and slow down by ~20x.
Ultimately, I don't think SSE2 was what killed MMX, but rather SSE, and specifically floating point. MMX not only didn't support floating point, but was also highly concentrated on 16-bit signed integers and secondarily 8-bit unsigned integers. Support for 32-bit integers was particularly lacking and pack/unpack conversions were a bottleneck. Trying to do 3D was cramped because doing so required fixed-point and MMX didn't have the same affordances as DSPs or NEON for rounding or implicit narrow/widen in operations, or even swizzles. SSE, on the other hand, was just straight floating point with standard automatic IEEE rounding and also had important added operations like swizzles and insert/extract. Thus, when 3D took off, SSE was far more useful than MMX.
MMX, however, still remained useful for a while for image and signal processing. SSE2 being twice as wide didn't help algorithms that couldn't use the greater width, such as 8x8 block motion prediction. Additionally, some CPUs at the time only had a 64-bit data path and had to split SSE2 ops, but because of their 4-1-1 decode template, could only decode one such instruction per cycle. The result was that code using the MMX registers could still run noticeably faster than with the SSE registers. This caused some confusion with the 64-bit version of Windows since Microsoft tried to say that x87/MMX shouldn't be used in long mode, but after queries from video processing companies had to document that the x87/MMX registers were enabled and context switched for user mode code.
POD was pretty rad :) Thanks for the reminder of it (and all the MMX marketing around it.)
I remember just after IBM got copper interconnects working, there was a stock market dive, I ranted to my partner about how crazy it is for a company to achieve a sought after goal and have its value decrease. The next day there was a news story about the entire market decline being stalled by the force of IBM buying back it's own shares. My partner told me I should rant less and invest more.
https://en.wikipedia.org/wiki/X86-64#Microarchitecture_level...
"Additional XMM (SSE) registers: Similarly, the number of 128-bit XMM registers (used for Streaming SIMD instructions) is also increased from 8 to 16...
"The original AMD64 architecture adopted Intel's SSE and SSE2 as core instructions."
https://en.wikipedia.org/wiki/X86-64
This wansn't v2?
I believe that only the first Pentium 3 core, Katmai, did this.
> This caused some confusion with the 64-bit version of Windows since Microsoft tried to say that x87/MMX shouldn't be used in long mode, but after queries from video processing companies had to document that the x87/MMX registers were enabled and context switched for user mode code.
I have some faint memories of hearing somewhere that Long Mode didn't support x87. I wonder if it is related to this early info you mention and it being Microsoft specific.
Subsequently, there were additional instructions added in SSE3, SSSE3, SSE4.1 and SSE4.2, which are all incorporated into the v2 ISA level (along with a few other instructions). Then all of these instructions were given 256-bit variants in AVX, and AVX2 adds some more vector instructions; these are incorporated into the v3 ISA level. And then along comes AVX-512 and naming just becomes a podge at that point...
No, the Pentium M (Banias / Dothan) also needed multiple cycles for basic SSE2 ops (with a few exceptions like PUNPCKLQDQ or PMOVMSKB), a full four years after Katmai (source: I owned one, and wrote SIMD on it for codec libraries).
No, all Pentium 3s as well as the Pentium M. Pentium 4 notably didn't suffer from it, but it of course had many, many, MANY other performance issues.
> I have some faint memories of hearing somewhere that Long Mode didn't support x87. I wonder if it is related to this early info you mention and it being Microsoft specific.
It was VM86 mode that Long Mode didn't support, which was one of the rumored reasons for removing 16-bit NTVDM support (among many). x87 and MMX were always supported in long mode and notably some libraries like OpenBLAS still use x87 instructions. Windows does prohibit use of x87/MMX in kernel mode where the need is negligible.
They already had a 16-bit software emulator for running NTVDM on other archs.
Apparently the real reason was they wanted to drop some software compatibility restrictions, like the small max size of HANDLE tables needed for 16-bit compat.
> No, all Pentium 3s as well as the Pentium M. Pentium 4 notably didn't suffer from it, but it of course had many, many, MANY other performance issues.
I had to google a bit to confirm this, and seems like I'm not the only one that understood it the way I did:
https://www.vogons.org/viewtopic.php?p=1360606#p1360606
https://www.vogons.org/viewtopic.php?p=1360620#p1360620
Basically, the info I knew was that Katmai had 128 Bits SSE registers but processed it as 2x64 Bits. That info is well reference pretty much everywhere. What is NOT explicitly mentioned is whenever Coppermine/Tualatin maintained that arrangement or had 128 Bits compute units for SSE, so the wording always made Katmai to look like an exception, as if everything else was 128 Bits.
The whole RAMBUS debacle... OTH DDR chipsets for Tualatin had shown what it was the end game for the P6 arch.
Also, the GF4MX was just a GeForce256 in a trenchcoat, and didn't have vertex shaders (at least exposed to software, the T&L engine was still a vertex shader like engine, only the code was all written by nvidia and loaded from ROM).
GF3 was a very short lived product, even more short lived than GF2MX.
And the crucial thing here is what at the time of GF/GF2 you still could buy a computer without a proper[0] 3D and video output acceleration[1], but by the time of GF4MX (which is GF2MX on the booster shoes and VPE - and only a year and half later at the worst) you have no option not to get a full package.
SIS died, 3Dfx died even earlier, S3 became irrelevant and Intel was for the button pushers till i945 - which came only in 2005.
And while at first the price even for GF4MX420 was ~$100 it did two things:
- pushed the price for GF2MX series well below $50 (and quite soon followed the suit) so anything now had DX7 level 3D on-board
- pushed the price for every other 3D accelerator waaay down
- made MPEG and video acceleration the default in everything
[0] emphasis on the 'proper'
[1] though you needed to get out of the way to do so, eg i815, G450 - but you could!
If you programmed PCs in the late 90s, you probably remember the 'Intel Inside Pentium with MMX' stickers. MMX was, of course, a big marketing buzzword, but it was also Intel's major attempt at bringing SIMD (Single Instruction, Multiple Data) instructions to mainstream desktop CPUs.
It's a rite of passage of every student that completes our software rendering module to start asking questions and exploring the topic of SIMD (Single Instruction, Multiple Data).
Today we have AVX-512, NEON, SVE, and GPUs with thousands of cores. These modern CPUs & extensions are the result of years of exploration, learning, market pressure, and they all bring with them a lot of history/retro-compatibility noise. As always, I like to look back and discuss different technologies by analyzing what was happening when they were just starting out. Back in the late 90s, many programmers were learning how to squeeze a few extra frames per second out of a Pentium MMX using hand-writing assembly for the first time.

Intel Pentium with MMX tech
Let's travel back to 1997 and see what programming MMX looked like!
MMX (MultiMedia eXtensions) was introduced with the Pentium MMX processor in 1997 for improved multimedia experience, including graphics, digital signal processing, audio, and modem software.
It was a set of 57 additional instructions built into the Pentium chip for enhanced performance. The CPU had to be switched into MMX mode, which turned the first 64 bits of the x86 eight 80-bit floating point registers into MMX registers.

Eight MMX registers (64 bits long)
The idea motivating the creation and the use of MMX was simple: instead of processing one integer at a time, the CPU processes several integers packed inside a single 64-bit register simultaneously.
For multimedia software (image processing, audio mixing, video playback, and even games) this could provide significant speedups.
For example, instead of adding eight bytes individually:
10 + 20
30 + 40
50 + 60
70 + 80
...
MMX lets us perform all eight additions with a single instruction.
That's parallelism! And that's the main idea behind SIMD.
"Single Instruction, Multiple Data" is a type of parallel processing technique. It describes computers with multiple processing elements that perform the same operation on multiple data points simultaneously.

SIMD can be internal (part of the hardware design) and it can be directly accessible through an instruction set architecture (ISA). In the case of the MMX, Intel extended their x86 instruction set to include the new SIMD instructions.
No, MMX was definitely not the first SIMD, although it was hugely important in making SIMD mainstream on x86 PCs.
We must remember that many advances and innovations in computer technology predates the personal computer era. SIMD is no different, and it predates Intel's MMX by decades. The general idea of using one instruction operating on multiple data elements goes back much further than the Pentium.
One famous early example is the ILLIAC IV supercomputer project from the 60s. The ILLIAC IV was the first massively parallel computer. The system was originally designed to have 256 64-bit floating-point units (FPUs) and four central processing units (CPUs) able to process 1 billion operations per second.

ILLIAC IV photo by Sascha Pohlflepp CC-BY-2.0
In summary, the ILLIAC IV used a large number of processing elements operating under a single instruction stream, making it an important ancestor of modern SIMD/vector architectures.

ILLIAC IV array overview
There were also array-processing machines from companies such as CDC, Cray, and others throughout the 60s and 80s.
It's important to point out that Vector Processing and SIMD are not quite the same thing. The distinction is mostly about how the multiple data elements are presented to the processor and how the hardware executes them.
SIMD operations are fundamentally tied to the width of the registers.

The Cray-1 from 1976 was characterized by being a vector processor. It had eight 64-element vector registers, each holding 64-bit values.

Cray-1 supercomputer
Similar to what we saw with SIMD, the Cray-1 did not require 64 separate ADD instructions. So, given that this is obviously SIMD-like, why don't we simply call it SIMD? Because of an architectural distinction. SIMD operates on fixed-width registers, while the vector register in the Cray-1 was more like a container for a sequence.

Vector processors are usually characterized by the presence of a SET VECTOR SIZE instruction. This vector functional unit can process the elements over multiple cycles.
The Cray-1 had a VL (vector length) register and instructions that could set that length. The vector functional units then processed that many elements over multiple cycles. All the remaining extra elements of the array were simply not used/cycled.

The VL register of the Cray-1 was 7 bits, even though the architecture only used values up to 64 (2⁶), given that Cray-1 vector registers contained up to 64 elements. Also, a small detail that is worth mentioning is the fact that the Cray-1 interpreted VL=0 as 64, processing all the 64 elements of the vector register.
So, as you can see, SIMD was not invented by Intel. Its real significance was putting a relatively accessible SIMD instruction set into mainstream x86 PCs. That was the first time most of us (personal computer enthusiasts) were able to get our hands dirty with this type of technology, and that is why MMX gets so much attention in retro-computing discussions. It wasn't the invention of SIMD, but it was one of the moments when SIMD crossed over from specialized/high-performance computing into ordinary desktop software development.
As we saw before, one clever design decision was that Intel didn't add a brand-new register file. Instead, MMX re-used the floating-point stack registers.
MM0
MM1
MM2
MM3
MM4
MM5
MM6
MM7
Each MMX register is 64 bits wide. Internally, the MMX registers were aliases of the x87 floating-point registers. These underlying floating-point registers were 80 bits long, but MMX accessed only the lower 64 bits.

Eight MMX registers (64 bits long)
This decision saved silicon, but it also created an important limitation:
SIMD and floating-point operations were mutually exclusive. Since MMX is basically "taking over" the x87 floating-point registers, MMX multimedia operations and floating-point operations could not coexist.
It also created an important rule:
Don't mix MMX and x87 floating-point code without cleaning up first.
Don't worry... we'll come back and talk more about these limitations very soon.
A single MMX register could hold different layouts:

The same register could represent any of these layouts depending on the instruction you used.
Suppose we want to add 8 pixels together (assuming no compiler optimization):
for (int i = 0; i < 8; i++) {
dest[i] = src1[i] + src2[i];
}
An MMX version would look like:
movq mm0, [src1] ; loads eight bytes
movq mm1, [src2] ; loads eight bytes
paddb mm0, mm1 ; performs eight byte additions simultaneously
movq [dst], mm0 ; store the result
One instruction replaced eight separate additions!
Another really great feature of MMX was its ability to perform "saturating" arithmetic.
When we add two values together, normal integer arithmetic wraps around.
250 + 20 // = 270, which wraps around to 14
The above addition exceeds 255 and will normally wrap around to 14.
This 'wrap around' nature of integer arithmetic can be cumbersome to deal with, especially when working with graphics. If we are adding pixel values together, we want maximum brightness to stay maximum brightness!
MMX provides saturation for cases like this. The instruction is simply:
paddusb mm0, mm1
The us stands for unsigned saturation, and clamps the maximum result value to 255.
250 + 20 // = 255
Likewise:
10 - 40 // = 0
This made brightness adjustments and blending operations much simpler.
It's natural to think of MMX's power applied to graphics and other multimedia tasks. For example, let's suppose we want to create a mask where every value greater than another becomes white.
MMX offers comparison instructions:
pcmpgtb mm0, mm1
Each byte becomes either 0xFF or 0x00.
These masks are useful when implementing threshold filters, sprite transparency, or collision logic.
If you ever took a course with us, you know how often we use bitshifting to multiply & divide values by 2. Given how expensive multiplication and (especially) division instructions were in older CPUs, shifting bits left & right was a faster way of multiplying and dividing values by powers of 2.
MMX offers a way of shifting packed values simultaneously:
psllw mm0, 1 ; multiply every 16-bit value by two
psrlw mm0, 1 ; divide by two
Again, all packed values are shifted at the same time!
Need to multiply packed values by something other than 2? Not a problem. MMX also supports packed multiplication.
pmullw mm0, mm1
Each pair of 16-bit integers is multiplied independently. This became useful for:
Remember how one of the big aspects of MMX was that it "took over" the floating-point registers of the x87? That means there was no floating-point SIMD with MMX, so MMX programs relied heavily on fixed-point arithmetic. Real floating-point SIMD for Intel x86 CPUs didn't arrive until SSE (Streaming SIMD Extensions) several years later with the Pentium III series.
Let's say you are responsible for writing a small routine to brighten an image. In this image, every pixel is one byte long. Without MMX, our code might look something like this:
int intensity = 20;
for (int i = 0; i < WIDTH * HEIGHT; i++) {
pixels[i] += intensity;
if (pixels[i] > 255) {
pixels[i] = 255;
}
}
Using MMX, the iterations over chunks of pixels would look something like:
section .data
; we put 8 bytes in memory with the value 20 (or 0x14 in hex)
intensity: times 8 db 20 ; 14 14 14 14 14 14 14 14
section .text
; esi = pointer to pixels
; ecx = number of 8-pixel chunks
loop:
movq mm0, [esi] ; load 8 pixels
movq mm1, [intensity] ; load eight 20s
paddusb mm0, mm1 ; saturated addition
movq [esi], mm0 ; store 8 pixels
add esi, 8 ; next 8 pixels
dec ecx
jnz loop
emms ; leave MMX state
With MMX, eight pixels are processed at once. This was exactly the sort of optimization that graphics libraries loved.
As we just learned, MMX "borrowed" the floating-point registers from the original x86 architecture.
That means when you're finished using MMX, you must tell the CPU that you're done.
emms
This clears the MMX state and allows x87 floating-point instructions to work correctly again.
If we forget to call EMMS, strange floating-point bugs may appear later in the program.
Trust me, every MMX programmer learned this lesson sooner or later.
Weirdly enough, not as many as you would think. Besides MMX being limited, you have to remember that MMX was released right around the time GPUs and 3D hardware acceleration cards (like S3 ViRGE, ATI 3D Rage, and 3dfx Voodoo) were entering the PC market. So, by the time MMX arrived, dedicated 3D hardware was already beginning to take over workloads that might otherwise have benefited from CPU-side optimizations.
That being said, during the late DOS and early Windows 95 era, CPUs were often the bottleneck for non-accelerated games.
MMX found its way into routines such as:
Kyle Freeman, the main programmer behind Novalogic's Comanche 3, often mentions how the game made heavy use of MMX instructions for both audio & graphics.

Kyle Freeman's tweet on Comanche 3 use of MMX
Examples of games that either required or benefited from MMX enhancements in their software renderer were Eraser Turnabout, POD, and Extreme Assault. I have read rumors of a Tomb Raider patch that added MMX enhancements to the game for lighting and textures, but I personally never saw any reliable source to confirm this version existed. Let me know if you do!
Even though Quake and Quake II are often used as examples of games that benefited from MMX technology, it's important to point out that Quake's famous software renderer predates MMX. The original Quake was released in 1996, while Intel introduced the Pentium MMX in 1997. So, Quake's fast performance was achieved primarily through techniques like register allocation, loop optimization, lookup tables, and hand-written x86; not by relying on MMX.
As we'll discuss soon, not many games actually used MMX. But the aplications that did, shipped with optional MMX code paths that were selected if the CPU supported the new instructions. Of course, developers usually maintained a non-MMX version as well, since millions of users still owned older Pentium processors.
Software could not simply assume MMX was available. The common approach was to use the CPUID instruction.
CPUID is an x86 CPU instruction used to ask the processor what it supports and to retrieve information about the CPU.
mov eax, 1
cpuid ; retrieve information about the CPU
test edx, 1 << 23 ; bit 23 of EDX indicates MMX support
jz NoMMX ; if zero, jump to routine to handle absence of MMX
Bit 23 of the EDX register indicates MMX support. If the bit is set, the program can safely execute MMX instructions. Otherwise, it should fall back to a scalar (non-MMX) implementation.
Other bits in EDX will tell us about different supported technologies. For example:
Years later, Quake II added support for a technology called 3DNow!. 3DNow! was designed by AMD and it was conceptually similar to MMX; it adds SIMD instructions to the base x86 instruction set, enabling floating-point SIMD operations using vector registers.

Quake II
3DNow! also used 64-bit MMX registers, but it added instructions oriented toward floating-point SIMD. The first processor to ship with 3DNow! technology was the AMD K6-2 from 1998.

AMD K6-2 with 3DNow!
The source code of Quake II 3.19 was released in 2001 by Id Software under a GPL licence. Just keep in mind that the 3DNow! code was not part of that original 3.19 source release. Instead, AMD/id Software distributed a separate 3DNow!-optimized Quake II v3.20 build. Of course, these were distributed as pre-compiled binaries (without source code).
No. Even though a few popular games added 3DNow! enhancements, 3DNow! was never more popular than MMX. In fact, MMX was substantially more widespread because it had a huge head start. Intel introduced MMX with the Pentium MMX in January 1997, while AMD's 3DNow! arrived with the K6-2 in 1998.
More importantly, Intel had an enormous share of the x86 market, so choosing MMX meant potentially supporting a larger fraction of PC gamers. At the end of the day, 3DNow! was an AMD-specific extension.
Developers faced the choice of supporting either Intel CPUs or AMD CPUs. If you wrote a 3DNow!-optimized renderer, it would only benefit AMD chips that supported it. Meanwhile, MMX was supported by Intel and subsequently by AMD, Cyrix, and others.
Finally, the ultimate blow to AMD's 3DNow! was Intel's introduction of SSE (Streaming SIMD Extensions) with the Pentium III in 1999.

Intel's SSE was a major turning point for both 3DNow! and MMX, but it didn't immediately kill MMX.
SSE technology introduced both scalar and packed floating-point instructions, which basically removed the old MMX limitation of not allowing floating-point code and SIMD to coexist.

SSE's threat to 3DNow! was bigger because it did the same fundamental job: floating-point SIMD, but with a more powerful and ultimately industry-standard architecture. SSE didn't immediately make MMX obsolete because MMX was still excellent at integer SIMD, particularly for things like pixels, image processing, audio samples, and packed 8/16-bit arithmetic.
SSE gave developers a more powerful and vendor-backed floating-point SIMD architecture. And unlike 3DNow!, SSE became the long-term direction of x86 SIMD.
So, what ultimately made the old MMX registers unnecessary? If you ask retro tech enthusiasts, most answers would point to the introduction of SSE2 by Intel in 2000. SSE2 brought exactly the thing MMX was good at (integer SIMD) into the newer XMM register architecture, while also giving programmers much wider registers.
SSE2 gives you 128-bit XMM registers and, crucially, adds extensive integer SIMD operations to the x86 architecture.

Imagine you were a game programmer in 2001.
Using the old MMX registers, you could process 8 pixels at a time:
movq mm0, [pixels]
paddusb mm0, mm1
movq [pixels], mm0
With SSE2, you could now process 16 pixels at a time:
movdqu xmm0, [pixels]
paddusb xmm0, xmm1
movdqu [pixels], xmm0
Hopefully, by now, you understand that MMX was not a magic "turbo button" that made the entire CPU faster. Many of my friends assumed that buying a fancy Pentium "with MMX technology" would make the entire computer (and especially their multimedia applications) faster. As you just saw, MMX simply gave the CPU new tools, but it didn't automatically make the old tools faster.
Of course, since the "Pentium with MMX" was a newer chip, it naturally came with improvements. The newer chips had 32KB of cache, while the older Pentiums only had 16KB of cache. Bigger cache alone can make your applications run faster, but that was not directly related to the presence or absence of MMX.
Looking back, MMX may seem tiny by today's standards. MMX had only eight 64-bit registers, only supported integer arithmetic, and there was no floating-point SIMD or 128-bit vectors.
MMX introduced ideas that are still with us today:
Later, SSE expanded the registers to 128 bits, and AVX doubled them again. Modern CPUs can process hundreds of bytes per instruction using vector extensions that all trace their lineage back to those original MMX instructions.
For many programmers in the late 1990s, MMX was the first exposure to thinking about algorithms in terms of vectors instead of individual values. Once you started seeing arrays as chunks of data that could move through the CPU together, it changed how you thought of performance-critical code.
Unlike many authors, I would not call MMX a failure. You'll see some comments claiming MMX was decent for digital signal processing (like audio and modem), but not that useful for games and renderers. For those applications, adding any MMX code meant lowering FPU performance on the graphics side, which limited the use of MMX in games.
My diplomatic answer would be that MMX was a successful technology transition, but not a particularly successful long-lived instruction set.
Even though MMX has long since disappeared from modern software, it remains an important milestone in the evolution of PC programming. It is a fascinating snapshot of an era when every CPU cycle counted.
And that's it for our quick review of MMX in old Pentium CPUs. If you have any suggestions for this article, you can yell at me on Twitter. Also, remember to visit the courses page to access my lectures on retro programming.
See you inside!