> O(n^2) is the sweet spot of badly scaling algorithms: fast enough to make it into production, but slow enough to make things fall down once it gets there.
https://randomascii.wordpress.com/2021/02/16/arranging-invis...
> O(n^2) is the sweet spot of badly scaling algorithms: fast enough to make it into production, but slow enough to make things fall down once it gets there.
https://randomascii.wordpress.com/2021/02/16/arranging-invis...
How I cut GTA Online loading times by 70% https://nee.lv/2021/02/28/How-I-cut-GTA-Online-loading-times...
I'm playing a game (Fenyx Rising) and after launching it I always wonder why "Checking for additional content" screen takes 20-30 seconds. I'm pretty sure it should be just a single request.
The runtime for the release day store inventory count may have been okay, but I don't think that Rockstar kept profiling their game every time that they modified their store inventory.
The amount of pain Rockstar inflicted on their 90K+ users shows that Rockstar didn't care that their game took more than 3 mins to startup for the majority of their users.
Given that our team was over an order of magnitude smaller than Rockstar, I would be very surprised if they did not have anyone even casually browsing a profiler every 3 months or something, though I think at their scale (LinkedIn claims 5k employees) they can probably have a team or two where everyone's entire job description be performance maintenance.
But the profiling is all done on the game loop; I've never heard of any teams profiling the startup...
Speaking from experience... Once, I was so annoyed at a "startup UI"-related dialog taking way (wayyyy) too long to load. I dropped an expletive into Slack and, not much later, I was guided toward fixing my own ~2 year old mistake. We were scanning some "user-generated files" in a way that worked fine but scaled horribly -- the operation went from multiple seconds down to milliseconds. Ugh.
"everytime this blog is linked, I end up reading the whole thing"
I don't think it was accidentally quadratic. More likely, it was exponential.
For something mission critical like a bootloader that's more valuable than turning O(n^2) into O(n log n). People running systems like BSD largely don't care how long the system takes to boot, once it's booted the system runs for years.
Source: written sorts in assembly more times than I would care to count.
You can't always rely on smarter people to be there to do things for you. And you also can't rely on the standard library to have been written by smarter people anyway, and even if so, to have been written by smarter people in order to handle the situation you find yourself in. There's lots of ways to sort, and lots of good reasons to choose ways that are less than optimal for the use cases they end up being used in.
This is just a moronic defense of the venerated FreeBSD developers, it’s on a level equal to organized religion. The FreeBSD developers are fine developers and this was dumb, that’s why they replaced it.
And in this day and age there really is no argument for any user space c environment to exist where the qsort standard library function is not available. And even if there was, it would still be smarter to just copy and paste the battletested code from the c library than write another implementation. Because that’s how you end up with bubblesort because doing it right is too hard.
The real ones, the ones that write USENIX papers.
Besides I would assume anyone getting ready for their leetcode assignments goes through them anyway.
Compared to the rest of the task, writing a sort is pretty darn trivial.
It seems to be any prolonged discussion about which sorting algorithm should be used is sort of skipping the elephant. Why is the list not sorted to begin with?
Without that basic knowledge it isn't very productive to focus on implementation details, no matter how fun the sorting exercise is. Deleted code is fast code.
One could argue that the database engine should be "smarter", but it's not. Note that data could be added or removed in the meantime, so the database engine can't really cache the result. See also https://stackoverflow.com/questions/70519518/is-there-any-be...
https://use-the-index-luke.com/sql/partial-results/fetch-nex...
Otherwise using offset usually is OK idea. Because users very rarely will inspect page #2153. They're interested with page 1, sometimes page 2. limit/offset works fine for those cases and it'll work for page 2153 for those who visit it once in a decade. Using ids makes logic to trac prev/next/page number incredibly complex and generally you don't need it.
Who is "you" here?
Usually what happens is that party A builds a REST API (or other connectionless protocol) for fetching a list of some entity-type; and they limit the number of items that can be fetched in one request (because they have a billion such entities, and they don't want to even try to imagine what kind of system would be necessary to generate and stream back a 5TB JSON response); which implies pagination, to get at "the rest of" the entities.
Then party B, a user of this API, decides that they want to suck party A's whole billion-entity database out through the straw of that REST API, by scraping their way through each page.
> it'll work for page 2153 for those who visit it once in a decade
To be looking at page 2153, the user probably first visited every page before 2153. Which means they didn't do one O(N) query (which would by itself be fine); but rather, they made O(N) requests that each did an O(N) query.
Using ids makes logic to trac prev/next/page number incredibly complex and generally you don't need it.
When it’s a public site, users may post so fast that this “next” can show some previous page. Paging via ids is a must there.
That is usually a non-issue. The cost in DB operations is usually much more relevant than it.
When people do actually care about fully enumeration and unicity of the items they they are querying, "pagination" itself tends to be a too messy concept.
As a result, a user (all of them) hits “next” again until a page looks like containing new posts. It’s multiple requests wasted.
Anyway, what exactly becomes messy?
A "page" is a very ill-defined thing that can only exist if your stuff is mostly static. Queries are very different on dynamic content.
Since there’s no evidence of a mess still, I believe you’re projecting it from an overthought side that isn’t real.
For good performance this also requires that your search criteria (if any) be compatible with this index.
it doesn't seem mentioned in the HN thread the cause here is probably the same thing O(n^2): sorting. laying out icons is only linear if the icons are read in the order that they're placed. It's been a long time since I used windows regularly but my memory is the default placement is by creation time. So if they're read off disk by filename (or some sort of hash) they'd need to be sorted by timestamp.
Reasons? Firstly, you’d have to know the entries are sorted. For that, you need an API that tells you that or hard-code information about file systems in your code. They may exist, but I’m not aware of any file system API that provides that information.
Secondly, the file system may not return the names sorted in the locale you want to sort them in.
Thirdly, the sorting code used in the file system may contain a bug. Once file systems are out there, you can’t fix them (happened in one of Apple’s file systems. HFS, IIRC)
Lastly, modern GUIs tend to sort file names containing numbers non-alphabetically, so that, for example “file 2.jpg” gets sorted before “file 12.jpg”.
So, I think it’s easier to always sort. I would pick an algorithm that works well when items are mostly sorted at the start, though.
(More realistically, below people are discussing that in the kernel environment the set of standard or third party library available may be unavoidably limited)
(I think this was the check) https://github.com/freebsd/freebsd-src/blob/main/lib/libc/st...
[0]: https://github.com/weiss/original-bsd/commit/d3fcf71e0db57cb...
[1]: https://cs.fit.edu/~pkc/classes/writing/papers/bentley93engi...
I mean, clearly it wasn't good enough otherwise they would have used it, no? Perhaps integrating it was a huge faff.
This makes no sense to me. What about C makes it hard to use a good library?
A lot of languages now have common tooling (cargo for rust, pip for python, etc) which makes it easier to find and incorporate the libraries you want. Apparently there are tools like https://conan.io/ but they're not as widely-adopted.
C's build system is similarly non-uniform. Many packages use Makefiles, others use different build mechanisms.
C has no universally-agreed error handling mechanism or convention. Whether exceptions or golang's error interface, you can generally assume that a random package in most languages will handle errors the way you expect. In C it's a lot more varied.
Similarly memory allocation - sometimes in a larger application you want to customize how malloc and friends work. How and whether you can do that for a C package is non-uniform.
Mind you the C standard library has a sort() function which will have sensible big-O behavior on pretty much any platform. I suspect this specific problem is more to do with this being kernel-mode code which has a lot of special conditions.
Really? Is it really considered hard to link a library without them? Am I so old that somehow I grew up in a world where linking a library was not considered black magic?
The first issue is actually downloading the dependencies, doing this manually quickly becomes infeasible for any non-trivial project.
The second issue is keeping everything updated, and making sure that all packages are compatible with all other packages. Doing this manually is also not easy.
With C specifically, you need to wrangle different build systems, and once you have them built and "installed", you need to figure out which linker and compiler flags are needed to consume the libraries.
If you are working on a small project with maybe a few dependencies you can do this by hand, but when you get to say, 15 dependencies, it quickly becomes very difficult.
You can use the system package manager on some systems to install libraries I guess (assuming it has the packages and versions that you need), in this case manually managing things could be a lot easier, but you still should be using pkg-config for portability purposes.
If the argument is really "it's impossible to make a good library in C", that's different. I'd very much disagree with that, but it would be to the point.
And I haven't mentioned the lack of a strong universal string type, the way many libraries typedef their own family of different kinds of integer, the way one library will require you to free() a returned structure and another will require you to call my_library_free()...
It all adds up to additional friction.
You don't have to agree! Maybe I am out of date, I haven't really dealt with this since the mid 2000's. I'd be thrilled to hear this isn't an issue any more.
It's not really a matter of whether or not I agree. I was just trying to understand what the assertion was!
I was baffled by the notion because I couldn't think of anything inherent in the language that made it hard to use good libraries. Now I understand that's not really what the assertion was.
With e.g. Java, Javascript, PHP, and Python it's clear to me.
With C, I don't know.
GOOD! I love that C lacks this pollution. Means that code is written for-purpose and tuned for-purpose.
Outside of that you don't need to deal with exceptions in a sorting library, and you can happily make it a single .c and .h
Now do it in C/C++. Absolute nightmare.
Look at how many header-only C++ libraries there are out there. They're making compilation time drastically worse purely to avoid the need to faff with a shitty build system. It's a good trade-off too. I use header-only libraries where possible. They're convenient and popular.
Actually vcpkg seems to be going some way to fixing that but I have yet to convince my co-workers to use it.
Then maybe don't use a shitty build system?
It's true, C is not trying to be a programming environment or tech stack. It's a language, that's it. Whether or not that's desirable depends on what you're trying to do, it's not something that is good or bad in some absolute sense.
You have your choice of build systems, so pick one that meets your needs.
Vcpkg isn't for me, either, because it doesn't solve any problem I have. If it does for you, awesome!
And can I pick the one that my dependencies use too? Didn't think so.
If the issue is that you don't like how the dependency has arranged to build (I'm not sure why you'd actually care, but just in case...), then port the makefile (or whatever) to your preferred system.
Or, another guess, is the issue that you want to build all your dependencies as if they were an integral part of your own project? If that's the case, I would argue that you're doing it wrong and another tech stack would make you happier.
Since we're talking FreeBSD here, thirty years ago we had Modula-3 whose build system is lightyears ahead of anything make based.
In the event you find yourself doing this constantly, write a shell script.
Again the problem is that no package/dependency management for C/C++ means that C is balkanized and more difficult to deal with compared to other languages. Using third party libraries in C is far more difficult and error prone than it ought to be.
Pretty much every language that provides more comprehensive dependency management also provides easy enough access to allow you to NIH/DIY it if you need/want to. For instance contrast this with something like rust where cargo is standard across all supported platforms and provides proper dependency management. You can still call the rust compiler (rustc) and whatever linker yourself if you so desire.
C and C++ dependency installation is "sub-optimal" but it is certainly well
understood. The fact that there are many cross platform, open source projects
written in C and C++ proves this.
Nope. It proves that people have found work arounds up to and including cargo culting things. Any large enough project is going to rely on something else to generate the makefiles, and if you depend on anything large enough you're gonna get stuck having to build and/or install whatever other makefile generators are required. Simply put it's an archaic mess. If you can't deal with makefiles and scripts, maybe stick to Go and Rust?
To be clear it's not that I can't deal with makefiles it's that I don't find it a good use of my time*. Take, for instance, the FreeBSD ports tree. make(1) is its achilles heal and the main reason it's obscenely slow to deal with (even compared to something like portage). Besides, doing anything by hand is inherently error prone be it bounds checking array access or cobbling together dependency management.* And, sure, in years past I wrote a drop-in replacement for automake/autoconf in perl whose big (speed) advantage was that it didn't spawn a new shell for each check. It was a neat hack, but that's all anything papering over the make(1) interface is.
Many people will in fact do just this.
Though this has not much relevance here as it is about assembly.
It's easy to lose sight of the climb once you're at the top.
It's a bit like criticizing a fish for having no legs.
The problem isn't that it doesn't need one, it's that it doesn't have one. I have no idea why you would think that it doesn't need one.
Well there is vcpkg now anyway so it finally does have one.
> I have no idea why you would think that it doesn't need one.
Is that I have no idea why anyone would think that it does need one.
Perhaps the disconnect is that you are wishing C addresses different use cases than it addresses? That you wish it were a different language? If so, that's fine. Use a more appropriate language for your task. I just find it odd if the criticism of C is that it isn't a different kind of language.
For the same reason any language needs one. What is it about C that you think excludes it from the basic requirement of "using third party libraries"?
# dnf install foo-develNot to mention kernel mode doesn't want a gigantic library package manager to pull in leftpad() from the internet. As mentioned, the kernel libraries on FreeBSD have a qsort, but they didn't in the original commit from 3 decades ago or whatever.
It doesn't have templates/generics.
curl_easy_option_by_name() is already up to 24 characters and there's only two "levels" in curl_easy_*.
Annoyance #2: There's no formal registrar for LIBNAME. This isn't a big deal for popular libraries, but it's a pain having to keep a locally-modified copy of a less popular dependency just because it shares its name with another less popular dependency.
Annoyance #3: LIBNAME_actual_function_name() is a pain to read and using either the preprocessor or static inlines to locally alias function names for the sake of readability is silly.
@2: Agree, although I don't recall this ever happening to me.
@3: Is it? How is libname::actual_function_name() much better?
I actually like to use libname__actual_function_name(), as it further separates "namespace" from function name (unless we need compatibility with C++, as IIRC it reserves all double underscores, not only at the beginning).
This is still the case in C11, Section 5.2.4.1. Did this change in the most recent standard?
> @2: Agree, although I don't recall this ever happening to me.
It happened to me once. I ran across a library from 1994 and another from the 2010s which shared a simple name like "libamc". I'll comb through my records later to figure out the actual name.
> @3: Is it? How is libname::actual_function_name() much better?
It's not, but I wasn't thinking of C++ specifically. (I don't know C++. I've somehow managed to avoid it in many years of writing C.)
I was thinking more like the file-local namespace clobbering offered by Python e.g., from LIBNAME import actual_function_name.
Well, no, it's still marked "just" obsolete. For it to be deprecated or removed there would need to be anybody caring about it enough to put some work. But since it doesn't affect vendors at all (they can just ignore) and users don't complain, it's just a forgotten "law" - still law, but a dead one.
> I was thinking more like the file-local namespace clobbering offered by Python e.g., from LIBNAME import actual_function_name.
Oh, that's... way more than just namespaces. Way more. That would require more fundamental changes and additions. C++ just added modules in C++20 (not sure how well those will catch on), but I don't think something like that is to be expected in C for feasible future.
Suppose I need to define a copy assignment operator for the library's sort function to use. Is there a good way to overload it? Can the library know what the size of each element is based on its type without having to pass it in as a parameter?
You can pass function pointers to the library, but that quickly becomes awful.
/* sort for basic integer types */
void lib_sort_u8(uint8_t *a, size_t count);
void lib_sort_i8(int8_t *a, size_t count);
void lib_sort_u16(uint16_t *a, size_t count);
void lib_sort_i16(int16_t *a, size_t count);
void lib_sort_u32(uint32_t *a, size_t count);
void lib_sort_i32(int32_t *a, size_t count);
void lib_sort_u64(uint64_t *a, size_t count);
void lib_sort_i64(int64_t *a, size_t count);
/* specify a comparator */
void lib_sort_comp(void *start, size_t nmemb, size_t size, int (*compar), const void *, const void *));
/* specify a comparator and an assignment operator */
void lib_sort_assign_comp(void *start, size_t nmemb, size_t size, void (*assign)(void *, const void *), int (*compar)(const void *, const void *));
/* specify a comparator, an assignment operator, and ... */
Or, you get one function that takes all of the arguments and have to define and pass in a bunch of function pointers and type size parameters that are each an opportunity for bugs or UB in order to sort a simple array of integers.If my type needs a custom assignment operator, I need each library I use to take that as an argument. One expects the function pointer to take the arguments in the order (src, dst), another as (dst, src), a third specifies the return value as int instead of void, a fourth takes the source argument as "void *" instead of "const void *" in case you want to implement move semantics and a fifth doesn't support custom assignment operators at all.
It's no surprise that people prefer to avoid this.
having said that this specific sort is somewhere deep in kernel boot land. and kernel code can't really use the standard library. I am not sure if there is a standard kernel sort.