Xor swap trick and add/sub swap trick (2010)
cottonvibes.blogspot.com
cottonvibes.blogspot.com
> especially since floating point operations are generally compiled to use the x87 fpu (so the floats will be moved back into gprs, and end up being slower than a regular swap)
gcc, at least, will use SSE rather than x87 by default on 64-bit x86. Looking at godbolt there's still a bunch of conversions that come out to about the same number of instructions, although I suspect doing it with SSE is probably faster than doing it with x87. Doing it with a temporary variable instead rather than coercing through a series of casts is probably even faster, though.
void swap(int& x, int& y);
void swap(float& x, float& y) {
swap((int&)x, (int&)y);
}
This is UB and may explode. Probably the only portable way to do it is a memcpy() [1].The strict aliasing rule exists because without it a C compiler has to second guess every load and store, which would have been horribly slow in the 80s and would be even worse now. It still ends up doing that for distinct pointers with the same type, which is why you can explicitly restrict them in newer language versions so the compiler can assume that those pointers wont alias.
The best way to do this in C (NOT C++) would be with unions, assuming you know the size of int and float for your platform.
The following would be valid C:
union swappable{
float f;
int i;
}
void swapF(float* x, float* y) {
swappable xs, ys;
xs.f = *x;
ys.f = *y;
swapI(&xs.i, &ys.i);
}
Given that they are using overloaded functions though, we're in C++ land where there is no correct way to achieve it (or maybe you could with char*, as that is allowed to alias?).Compilers will optimize all of this to the same machine code.
In python you can just do: y, x = x, y
ag '(^|[^a-zA-Z0-9=])([a-zA-Z_][a-zA-Z0-9_]*), ?([a-zA-Z_][a-zA-Z0-9_]*) ?= ?\3, ?\2'
across several Python code bases I've worked on, and indeed found very few examples.Here's one from Python's statistics.py:
X, Y = self, other
if (Y._sigma, Y._mu) < (X._sigma, X._mu): # sort to assure commutativity
X, Y = Y, X
NumPy has only one match that isn't in test code, in numpy/core/numeric.py:convolve if (len(v) > len(a)):
a, v = v, a
It's a bit more common in SciPy, like scipy/linalg/blas.py:_get_funcs which has: if prefer_fortran:
module1, module2 = module2, module1
but even then, there are only 26 relevant matches across 421KLoC. > ag '(^|[^a-zA-Z0-9=])([a-zA-Z_][a-zA-Z0-9_]*), ?([a-zA-Z_][a-zA-Z0-9_]*) ?= ?\3, ?\2'
Why are you checking `(^|[^a-zA-Z0-9=])` before the first variable? Why are you storing it? Wouldn't `^\s*` (beginning of line, then 0 or more whitespace, not stored) be simpler? I ask to learn, not critically. return open(filename, mode, encoding=encoding, errors=errors)
^^^^^^^^^^^^^^^^^^^^^^^
This case matches because of the under-caret'ed e, encoding=encoding, e
You are right that requiring only leading whitespace would be clearer. I didn't think of it. I only wanted to eliminate those false positives I was seeing with my original test, which only had \2, \1.In thinking more about it, your whitespace-only gives a false negative for constructs like:
progressbar/widgets.py:306: if not self.fill_left: rpad, lpad = lpad, rpad
matplotlib/lib/matplotlib/widgets.py:1093: if vmin>vmax: vmin, vmax = vmax, vmin
The reason I stored \1 was simply to get the grouping. I didn't recall the syntax for non-capturing groups, and it wasn't worthwhile to look up. (a, b) = (b, a)
so the grouping stands out better. Turns out I didn't find any examples of that.> list($a, $b) = [$b, $a];
Which is a bit more verbose than what Python does, but it's the same thing under the hood - you create a list/tuple, create a new list with swapped items at specific indices and then dissolve that list back to local variables.
In the end, this is more expensive than the swap you'd do in C++^W other languages (EDIT: apparently C++ can also do this now), since it involves tuple creation. Although the compiler/JIT/interpreter will most likely recognize this pattern and optimize it to a `swap` call.
[$a, $b] = [$b, $a];BTW it's not "creating a tuple with syntactic sugar on the fly", I mean, if you think that's creating a tuple, obviously in lots of languages, that tuple is created by passing x/y's value, assigning to that anonymous tuple won't affect original x/y.
The correct term should be destructuring assignment.
That article is talking about performance, and as many have pointed out, it's better just use a 3rd variable(when there's no destructuring assignment) to swap and let compiler do its work.
I'm not saying syntax sugar is a bad thing.
say more?
Better to just use more variables and let the register allocator in the compiler decide what to do. If it’s a loop then unrolling it once could remove the need for any swapping at all, for example.
This is only true for AMD cpus, on Intel xchg is 3 uops. Still better than the xor trick, though.
In theory, maybe.
But if that happens in your application and is performance critical, you probably should change it such that you're swapping pointers to them instead...
a = load(ap)
b = load(bp)
store(ap, b)
store(bp, a)
ap += step; bp += step;
any instructions to "do the swap" are a waste because generally load-store are separate instructions in SIMD instruction sets (and even if that wasn't the case, that's how they would get executed anyway)if you want to avoid polluting the cache there are SSE instructions for loading without caching, which might be worthwhile
edit: this might be useful in a SIMD context where you need to swap two registers, where the cost of using another register is higher than the cost of the 3 arithmetic instructions. i could totally imagine that happening, but it's nothing to do with caches or memory
Watch your steps! The swap with floats relies on sizeof( float ) == sizeof( int ) which could not always be the case. Yes, you can create a set of macros and conditional compilation stanzas to cope with that and cover also char, short, long, long long, double and long double.
Finally, the explanation on why it works would be a great supplement to the article.
x -= y // x-y, y
y += x // x-y, x
x -= y // -y, x
If (x, y) represents a 2d vector this operation rotates the vector counterclockwise 90°.I guess you can make an argument that the best way to get an array sorted is to have the array already sorted, but that sounds as "the best way to calculate the length of the hypotenuse is to just measure the hypotenuse, and Pythagoras never realized it was a problem upstream".
Still, it's a neat trick.