1,017 karma · joined October 25, 2013
For the commonly used hash tables with prime size that use modulo to turn the hash code into a slot index, an identity hash for integers is usually fine (unless many integers are multiples of the prime size).
But other hash tables use power-of-two size to replace the modulo operation with a faster bit-and operation. Now an identity hash for integers is much more problematic, e.g. if all integers are multiples of 1000, only 1/8th of the table slots can be used.
The latter kind of hash tables would like all bits in the hash value to be well-distributed; and this is typically not true of the underlying integers. So an additional mixing operation needs to be used. Whether that mixing happens in the hash function or in the hash table depends on the implementation (for some, it's even configurable, e.g. is_avalanching marker in ankerl::unordered_dense).
The last byte of an emoji is never the same as 'a'. UTF-8 is self-synchronizing, a trailing byte can never be misinterpreted as the start of a new codepoint.
This makes `memmem()` a valid substring search on UTF-8! With most legacy multi-byte encodings this would fail, but with UTF-8 it works!
template<char... FILE>
struct magic { };
magic<__FILE__> m;
This does not compile with gcc, and std::source_location fails to work for exactly the same reason. I don't see any additional problems with std::source_location that don't equally apply to __FILE__.Here I could get `magic_functor<std::source_location::file_name()>::type` to work on MSVC: https://godbolt.org/z/Ph8dd78fa gcc doesn't accept this code, but that seems more like an implementation problem than a specification problem?
It can also be used in an NSDMI, in which case it ends up being instantiated whenever the NSDMI is used, i.e. at every aggregate initialization site.
https://godbolt.org/z/v5rhjqdbY
Of course, that doesn't really allow you to do anything that you couldn't already do in the with a constexpr function (see below). In particular, such a struct can be used as a non-type template parameter, but a `template<NSDMI sloc = NSDMI{}> void foo();` will merely report the line where the template is defined, not where `foo` is called.
// Results in a compile-time constant, so can be used everywhere where __LINE__ could be used.
constexpr int line(std::source_location loc = std::source_location::current()) {
return loc.line();
}
So std::source_location can fully replace __FILE__/__LINE__, but it cannot always replace the macro where __FILE__/__LINE__ were being used.The lowest point of the new orbit (perigee) is guaranteed to be no higher than the point of collision. Fragments after the collision have no further propulsion, so their new orbit must initially include the point of collision, and can only decay from there. This can also be seen on your scatterplot.
Moreover, the time to decay is most strongly influenced by the perigee, as the atmosphere is the strongest there. If a satellite on an low orbit (decay in decades) explodes, those fragments that "reach a higher orbit" will still (due to the low perigee) decay back to a near-circular orbit in decades. A collision can increase the lifetime for some of its fragments, but not by multiple orders of magnitude (unlike a circular higher orbit).
Your video link also nicely shows that fragments with a low perigee will decay quickly, no matter how high their apogee is.
E.g. Microsoft Edge on first launch can import bookmarks+stored passwords from Firefox (AFAIK without any user interaction, unless I clicked without thinking), and it also defaults to uploading this data to the Microsoft cloud (unless you're using a local account?).
C# has had `enumerable.AsParallel()` since .NET 4.0 (2010). Rust has rayon `input.par_iter()`.
Making an svn branch and merging it, now that was a huge issue. "svn merge" sucked compared to "git merge".
For the project I was managing back in 2007 I started using git-svn, because importing Subversion commits into git, using "git merge", and then exporting commits back to the subversion server, was faster and worked better than "svn merge" did (back in 2007).
In fact, it worked so well, I stopped using "svn merge" (which took 5+ minutes on our repository for every merge), and started using "git merge" with git-svn instead (which reduced the merge time to <3s, and even the extra git<->svn sync overhead cost only 30s or so). As a bonus, git also reported fewer "merge conflicts" (svn at the time had issues repeatedly merging from a branch to trunk).
So when I ended up picking a DVCS for another project, git was the natural choice since I already knew it. I imagine there are a lot of developers who started out on SVN and took a similar route to learning git, so having a high-quality Subversion bridge turned out to be one of the critical features on the road to adoption. This advantage in adoption then snowballed via forges like GitHub.
In 2010, python2.7 was released, backporting a bunch of python3 features. In 2012, python3.3 was released, fixing a bunch of needless incompatibilities. This finally made it viable to support both 2.7 and 3.3 in the same code base. For the most part, the migration of the Python library ecosystem only started in/after 2012.
python3 wasn't really a viable choice for application development until most libraries were ported (2014? 2015?).
Even inside a C extension where the Python API feels like it gives you control over when you release the GIL (with functions you'd have to call explicitly to release the GIL), it turns out that:
* any operation that allocates new Python objects might trigger garbage collection
* garbage collection may run `__del__` of objects completely unrelated to the currently running C code
* `__del__` can be implemented in python, thus releasing the GIL between bytecode instructions
Thus there's a lot of (rarely exercised) potential for concurrency even in C extensions that don't explicitly release the GIL themselves. nogil will make it easier to trigger data race bugs, but many of them will already have been theoretically possible before.
In this particular case, the Windows APIs have neither getaddrinfo() nor getenv(); and the closest equivalent GetEnvironmentVariableW is perfectly thread-safe. Microsoft additionally has a C runtime (msvcrt) providing functions like getenv(), but this is much less fundamental than it is on other system. Every program is supposed to ship its own copy of the C runtime, it's not officially part of Windows! And it's perfectly possible for multiple different copies of the C runtime to be loaded into the same Windows process. And since *environ is a variable defined by the C runtime, there's a different copy for each C runtime...
Is it just that the kernel doesn't really communicate TCP RST all that well to the application, so the HTTP server continues to count the connection against the "open connection limit" even though it isn't open anymore?
It's essentially a dummy parameter that serves as proof that the GIL is held. You need to provide it to PyO3 when calling back into the Python runtime, that way the compiler ensures the GIL is still held where required.
A Python script creating 10000 empty files takes 1.2s on C:, but only 0.5s on D:. Both are NTFS partitions on the same SSD, both partitions are using the default settings. (but the defaults differ: on the system drive Windows enables compatibility features such as 8dot3names, on additional partitions it does not)
Rewriting most/all of the Python analyses in a different (GIL-free language) is a no-go, the analyses have accumulated over the years and now there's more than a thousand of them. It would consume all our development resources for the next ~5 years. In retrospect I can say that choosing Python for these was major mistake, but it's one that cannot be fixed without a company-killing rewrite :(
We actually invested several months of developer time in allocating our core data structure in shared memory, allowing us to parallelize with multiprocessing. But there's still a whole bunch of ancillary data structures written in C++ that are not so easy to put in shared memory, so all analyses touching those are limited to a single process, which by Amdahl's law immediately starting dominating our execution time.
In the end, we ended up with quite a number of layers wrapping each other:
1. actual C++ implementation
2. actual C++ header
3. C++ wrapper implementation, avoiding constructs that Cython doesn't support
4. C++ wrapper header
5. Cython .pxd for step 4
6. Cython .pyx exposing `cdef class`es to Python with a nice Python-style API for the original C++ library.
7. Hand-written .pyi for type checking the remaining Python code, because Cython doesn't have support for auto-generating these yet.
Had we used pybind11 / nanobind instead, we could have stopped at step 3. Cython started easy, but ended up being a major maintenance burden.Previously this required rewriting the whole C++ library to support either pickling (multiplying the total memory consumption by the number of cores), or support allocating everything in shared memory (which means normal C++ types like `std::string` are unusable, need to switch e.g. to boost::interprocess).
Now is sufficient to pickle a pointer to a C++ object as an integer, and it'll still be a valid pointer in the other subinterpreter.
e.g. the interaction between subtyping (e.g. inheritance) and generics (with variance) is tricky: https://www.cis.upenn.edu/~bcpierce/papers/variance.pdf
It can be highly nontrivial to tell if a language actually has a Turing complete type system: the 2007 Kennedy&Pierce paper made it likely that java was turing complete; but it took until 2016 until it was finally proven that to be turing complete (https://arxiv.org/abs/1605.05274).
> What would it take a for a type system to NOT become Turing complete?
An analysis of all possible interactions between all features in the type system, building a formal proof that the type system is not turing complete. This is not really realistic for the style of complex generic type systems that programmers are now used to, it would need to be a vastly simpler language.
This is part of why Rust is so successful -- it's the first real alternative for this space since C++ came along. For most application development, it's better to use a garbage collected language. But in the application space there is a much bigger choice of languages already available, Rust wouldn't have been a big deal over there.
Because if you want to disable all UB-based optimizations, there's already a compiler flag that does that: -O0.
Seriously, in a language as low level as C++, every optimization needs some form of assumptions, and UB is how we currently allow compilers to make those assumptions.
Example: because it's undefined behavior to read pointers out-of-bounds, it is not possible for a valid program to scan a whole stack frame, so it is okay for the compiler to remove variables from the stack frame and instead store them in registers.
Without UB-optimizations, you would need some others justification of why it is okay for the compiler to change the result of a stack scan. Or use -O0 which keeps all variables on the stack. (but that certainly will be slower!)
From clang's point of view, a read from uninitialized memory returns the special value `undef`. Many operations applied to `undef` also return `undef`, but some (e.g. branching `if (undef)`) are outright UB. But the original example doesn't do anything with `undef`, so it's not clang-UB. The compiler generated valid code for the function, it just happens to pick "whatever was previously in RAX" as the value read from the uninitialized memory. The malloc call is optimized out because it's no longer necessary after the uninitialized read was replaced (the same will happen to any other malloc call where the return value is unused).
Note that the reason for this weird `undef` semantics ("almost but not quite UB yet") is that padding bytes are also uninitialized, but copying a whole struct incl. padding (even a memcpy-style byte-wise copy) must be defined behavior.