> I wouldn't be surprised if "parse and compile 8MB of integer literals" isn't a very well optimized code path in the compiler, because nobody is parsing and compiling 8MB of integer literals outside of artificial exercises like this
Actually, no. There is no portable "incbin" in C/C++, so one way to bake assets into the .rodata section is to convert a binary file into a huge "const unsigned char data[8192567] = { 0xFF, 0xD8, 0xFF, 0xE0, ... }" string and stash it into a .h or .c file. And yes, people do that often enough that gcc and clang actually had to optimize for this case specifically.
C23 gets #embed https://thephd.dev/finally-embed-in-c23 and in practice later C++ compilers will presumably also just do #embed rather than pretend they aren't the same code anyway.
However, even though there's a clear need, as that post explains, the compilers varied between "Bad at this" and "Completely awful at this" which is part of what was so frustrating for JeanHyde over years of getting this through committee.
In principle #embed is just shoving the bytes in as comma separated values, but in practice by the "as if" rule in C and C++ the compiler won't do that because it's stupid.
Ha, cute, I think I began examining details of include_bytes! only this year so it's possible I happened to first look at this soon after the big improvements landed in the release I was testing against.
Also, a reminder that JS engines do regularly have to contend with enormous JSON and do not generally display pathological behavior for large literals.
I'm not sure who posted that comment, but if they are on the Swift team and blaming users, that should stop. Code like that is out there, and it's the job of tooling to handle it gracefully.
> JS engines do regularly have to contend with enormous JSON and do not generally display pathological behavior for large literals
Actually it’s common advice to replace very large literals with `JSON.parse(“…”)` because it’s faster according to Chrome engineers [1]. At Notion we did so for our emoji unicode tables for a noticeable time-to-interactive improvement over the large literal.
It wasn’t a Swift team member, Swift community has a quick response for these cases because it’s been around forever and is unique enough of a “simple” case that it, ex. ends up at #1 on HN.
My understanding of Swift’s teams thoughts, though a few years out of date, is it’s very very hard to get the Swift type inference engine to handle a huge blob, without type hinting, or with literals that could be multiple types.
Three years ago I tried putting constants for a matrix (I think it was 1024x80 or sth like that) into a strongly typed array of floats. Still took the compiler 30mins to compile it. In the end I split the matrix up in rows, with each row being one variable, and a final variable to concatenate the rows. No typing hint whatsoever helped.
Yeah this one's real strange to me. I feel like it used to be as simple as the [int] definition people suggest in the thread.
I don't miss Xcode too much. IIRC I built some custom script using arcanda dropped by Apple employees to output per-function build times. It was great! I knew exactly what functions were slow. But now I am old and lazy lol.
Right, but unlike Swift, the type system in JS is not doing computationally expensive type checking for each element. This is the equivalent of creating 1M DOM trees in JS and expecting it to be responsive.
Rather than trying to force Swift to create fixed dimension arrays, the uncomfortable part about Swift for control freaks is that you should just make the array dynamic, and trust that the compiler is smart.
It's not blaming the user, but the compiler team has to balance functionality ("Why can't the type system give me a better error?") and speed. There's no way they can handle every pathological edge case gracefully.
So, an hour's worth of compilation time = 3600 seconds, divided by a million elements, that's 3.6 ms, or about 10 million instructions per element. Just what in the heck is this compiler doing with 10 million instructions on an integer literal? Like, it's never seen integer literals before? And they're all integers. Every element. It's not a "pathological edge case". The whole thread is absurd. People are speaking up and blaming programmers, the language, the type system, when this is so obviously the compiler's fault.
I have no idea what's going on under the hood, but I'm sure it's not a linear time process, and all it takes is one of those elements to be "109.1" to flip 1M elements from Ints to Doubles. Yes, they could special-case this (and arrays of Doubles, and Strings), but where do you draw the line? That's now extra code to maintain in the compiler just to support a behavior that should be discouraged. If you need to read in 1M integers, put them in a file and read them in.
> should be discouraged. If you need to read in 1M integers, put them in a file and read them in.
This is exactly the attitude that needs to be addressed. It's hostile to argue with users who are doing something completely legal and tell them they shouldn't do that, they should do it another way, that that behavior needs to be discouraged, etc.
We went through this all the time with V8, trying to tell JS developers they just shouldn't do that because V8 had couldn't run that code fast. Or worse, that it had a particular pathology that made certain code absurdly slow. It just doesn't fly. It's V8's job to not go off the rails for user inputs; it should provide good default performance all the time, not get stuck in deopt loops, use absurd amounts of memory, etc. Yeah, and that's hard work.
I hear you, ideally the compiler should be able to manage any arbitrary input in a reasonable time, and catch itself if necessary. Usually it does—sometimes with complex ungrouped arithmetic operations with ambiguous types, Swift will error out and tell you to break up your expression, rather than get stuck.
I would love for the Swift compiler to be dramatically faster, but I understand the challenge, with a powerful type inference engine that supports Generics. It's a resource scarcity problem. If the Swift team spends 10 hours to handle long strings of integer literals, that's 10 hours they haven't put toward features that would benefit a larger audience.
Your take sounds more reasonable. I would think you would get diminishing returns if you try to solve all pathological cases.
Sometimes it is the fault of the language design too. Maybe the language spec needs to be changed. Imagine all the wasted effort to optimize V8 when you could have put static typing into Javascript itself.
Indeed—Chris Lattner has mentioned recently that in retrospect, he regrets certain Swift design decisions which have made the compiler so complex and relatively slow.
For a compiled language it seems reasonable to assume that "put them in a file and read them in" means at compile time, like the C23 pre-processor feature #embed and the Rust macro include_bytes!
Now, #embed and include_bytes! always give you bytes (in Rust these are definitely u8, an unsigned 8-bit integer, I don't know what is promised in C but in practice I expect you get the same) because that's what modern files are - whereas maybe you want, say, 32-bit big endian signed integers in this Swift example. But, Swift is a higher level language, it's OK if it has some more nuance here and maybe pays a small performance penalty for it. 10% slower wouldn't be objectionable if we can specify the type of data for example.
I once had good reason (or at least I thought so!) to code-generate nested structures into .rodata by way of thousands of static arrays (each with its own variable), referenced by other static arrays, referenced by static structs…
The application was a highly optimized encoding of Aho-Corasick state machines for megabytes of static dictionary strings. The code generation (not in C) was trivial, and along with .rodata came all the benefits of shared pages and lazy loading.
Across a number of compilers I only ran into one bug in 32-bit gcc, which was worked around easily by disabling the (unhelpful) optimization pass that was getting snarled.
You're right of course but experienced developers know to write those as strings instead of arrays, especially for very large content. Otherwise both the compiler and IDE become painfully slow. ie your example would be written "\xFF\xD8\xFF\xE0..."
Don't try that in MSVC. It deliberately chokes on large strings. If you work hard you might put a few thousand integers in a string, but this example has a million integers. Most ways you can attempt that in MSVC time out, abort compilation or emit an error.
For an example of C using this in real life open a .xpm file in a text editor. There are many implementations of a C tool that converts a binary file into a .h.
And yes, it is dumb that we have to do tricks like this in 2023.
> Actually, no. There is no portable "incbin" in C/C++
I kinda hate this kind of argument. I mean, it's true, as .incbin is a GNU assembler directive (FWIW: binutils/llvm objcopy is a better mechanism still for this sort of thing in most contexts, as it doesn't involve source compilation of any kind).
But it leads ridiculous design decisions, like "I'm going to write 8MB of source code instead of doing the portability work on my own to turn that data into a linkable symbol".
There's a huge gulf in the space between "non-portable" and "impossible". In this case, the problem is trivially solved on every platform ever. Trivial problems should employ trivial solutions, even where they have to involve some per-platform engineering.
But it leads ridiculous design decisions, like "I'm going to write 8MB of source code instead of doing the portability work on my own to turn that data into a linkable symbol".
Why is that ridiculous? It strikes me as not necessarily the best, but the most obvious approach, the most portable, and possibly the quickest to implement.
Your toolchain might have a special way to import binary blobs, but a) you’ll have to dig through the docs to find it, b) you’ll probably need to solve the problem again when porting to a different platform, and c) who knows if it actually works, or if there are hidden gotchas?
Sure, if there’s a known tool or option that does the job, you should go ahead and use it. But in general, writing a little script to generate a bunch of boilerplate code is perfectly workable.
> Your toolchain might have a special way to import binary blobs, but a) you’ll have to dig through the docs to find it
This is a corrollary: "I don't want to learn my tools, so I'll learn the language standard instead" is fundamentally exactly the problem I'm talking about.
Straight up: C linkage is a 1970's paradigm full of tools that had to run on a PDP/11, and it's vastly simpler than learning C++ or Rust. It's just not "modern" and no one taught it to you, so it looks weird and mysterious. That's the problem!
I go back and forth on this argument when it comes to codegen. Like, you could make the same argument that protobuf shouldn't output C code. It should output an object file that you can link into whatever compiled language you want. C, fortran, C++, rust, who cares. As you say, the linking model is simple and works well.
Why do we generate big C/C++ strings instead, and compile those? Because object files have lots of compiler/platform/architecture specific stuff in them. Outputting C (or C++) then compiling it is a much more convenient way to generate those object files, because it works on every OS, compiler and architecture. Even systems that you don't know about, or that don't exist yet.
I hear what you're saying and I'm torn about it. I mean, aren't binary blobs just a simpler version of the problem protobuf faces? C code is already the most portable way to make object files. Why wouldn't we use it?
> Like, you could make the same argument that protobuf shouldn't output C code.
The case at hand is an 8MB static array of integers. Obviously yes, of course, absolutely: you choose the correct/simple/obvious/trivialest implementation strategy for the problem. That's exactly what I'm saying!
In the case of protobuf (static generation of an otherwise arbitrarily complicated data structure with reasonably bounded size), code generation makes a ton of sense.
And the simple/obvious/trivialest solution is to write an array literal with 4 millions integers in it, while fighting with Microsoft's link.exe is anything but. Even using rc.exe and loading that data from the resource section is a non-trivial amount of additional work.
Come on. Both binutils and nasm can generate perfectly working PE object files. I don't know the answer off the top of my head, but I bet anything even pure MSVC has a simple answer here. Dealing with the nonsense in the linked article is something you do for a quick hack or to test compiler performance, but (as demonstrated!) it scales poorly. It's terrible engineering, period. Use the right tools, even if they aren't ISO-specified. And if you can't or won't, please don't get into fights on the internet justifying the resulting hackery.
> I bet anything even pure MSVC has a simple answer here
You lose your bet, because it doesn't, neither its inline assembler nor actual MASM shipped with Visual Studio support any "incbin"-like directives. I guess you can generate an .asm with a huge db/dd, I guess, if you don't like a large literal array in .c files, but that's it.
> Come on. Both binutils and nasm can generate perfectly working PE object files. I don't know the answer off the top of my head, but I bet anything even pure MSVC has a simple answer here.
Right, meaning you have to implement N solutions instead of just one. It's a common enough and useful enough feature for the language to support it. I think it would be a different story if linkers were covered by the language specification.
I remain shocked at how controversial this is. Yes. Yes, implementing N trivial and easily maintained solutions is clearly better than one portable hack.
Clearly many people disagree. And given that C now has #embed, I don't even think I'd consider it to be a hack.
> I remain shocked at how controversial this is.
I am a bit shocked that you think the right solution to making data statically available to the rest of your program is somehow outside the scope of the programming language.
The comparison wasn't to #embed[1], but to an 8MB static array. You're winning an argument against a strawman, not me. For the record, I think #embed (given tooling that supports it) would be an excellent choice! That's not a defense of the technique under discussion though.
[1] Which FWIW is much less portable as an issue of practical engineering than assembler or binutils tooling!
It's "I don't want to learn and debug _everybody else who may possible want to build this otherwise portable C program_'s tools."
When those tools change how they do this unportable thing every couple of years in subtle and incompatible ways, which require #ifdef's to handle the different ways those linked against symbols can be accessed, multiplied by dozens of different platforms, then yes, I'm going to compile an 8MB literal.
I am pretty certain I've seen linkers routinely writing 0 instead of symbols' actual sizes so getting the actual size of the embedded binary blob is not very pretty.
It's just not "modern" and no one taught it to you, so it looks weird and mysterious.
I don’t know what to say except that I’ve worked with C linkers for a long time, since before I learned C++ and before Rust even existed, and I still don’t like ‘em.
> But it leads ridiculous design decisions, like "I'm going to write 8MB of source code instead of doing the portability work on my own to turn that data into a linkable symbol".
people use scripts to do this kind of thing and never think about it again, and it's still more portable than writing a custom build step.
> FWIW: binutils/llvm objcopy is a better mechanism still for this sort of thing in most contexts, as it doesn't involve source compilation of any kind
I used to agree with that, but honestly a simple `xxd` to get a C array avoids so many issues with `objcopy` that I'd rather just use that now. With `objcopy` even just getting the names of the produced symbols to be consistent is a pain, and you have to specify the output target and architecture which is just another thing you have to update for more platforms (and if someone's using a cross-compiler, they have to override that setting too).
In contrast if you just produce a C array then it compiles like normal C code and links like normal C code, problem solved and all the complexity is gone.
Lots of uses on that thread are suggesting this is some sort of design constraint or tradeoff - but I really am not sure I agree.
I understand this might not be a high priority, because it's a somewhat contrived usecase rare to matter in "the real world", but I strongly suspect this must be due to bug(s) in the compiler. I cannot think of a single reason it _has_ to be this way per the design of the language.
I recently started a macOS app project to learn Swift and SwiftUI. A week or two into the project, I managed to hit an honest-to-god compiler bug. A particular combination of syntaxes would reliably cause the compiler to crash (not yield a compile error; crash). I tried restarting XCode, restarting my computer, updating everything. Whenever I brought that syntax into the editor, XCode's Swift compiler integration would crash.
It wasn't anything exotic; I was making a straightforward app, and wasn't trying any Swift language funny-business (I didn't know the language well enough to even try)
It was bizarre to so easily find a bug in the headlining compiler made by the world's most valuable company; I can only think it's the result of this being a language that (effectively) only targets one company's systems, and the effect that has on the size and involvement of the community
I've also hit these kinds of issues with Swift/SwiftUI. It's always surprising for a production compiler to die and throw up it's hands and say "you hit a compiler bug!"
Type checking in Swift is notoriously slow and picky. Things have improved over the years (considerably!), but the odd complicated generics usecase will either take a minute extra to compile, or throw a Segfault.
It says "not all", which I interpret as "some of it is not spent in the type checker". I'm also not sure the times Xcode displays in the screenshot are bound to be accurate.
All profiling techniques have their drawbacks, but for a quick "where is the time being spent" analysis at multiple-second granularity, you're solidly in the territory where sampling profilers work really well.
There are different algorithms for type checking that impose different requirements on users.
The Java approach is roughly: write types everywhere, keep them simple (and limited in scope), it's easy for the compiler to infer the types.
The Haskell/ML approach is roughly: know a lot about strong type systems, be purely functional, infer most of the types, produce bad error messages when you can't.
The Swift approach attempts to be the best of both worlds, lots of inference, strong capabilities in expressing complex types, but the trade-off is that it can't use the inference algorithms of either of the above examples. I believe Swift necessarily has poor time complexity in type checking – it can't be better with the design it has and requirements it puts on authors.
On the contrary, Rust's type system moves more of that effort on to the developer – there's a reason why Rust has a much steeper learning curve than Swift.
The trade-off is a somewhat rare sharp edge for Swift and a much easier onboarding, vs Rusts noticeably harder onboarding and more predictable sharp edges.
Is there anyone on the compiler team that also plays a role in llvm development and optimizations? Or is there anyone on the llvm core team that cares about optimizations that would directly benefit Swift? If there's no one available that either can or is willing to optimize llvm, this feels like a worst case scenario. I'm curious what steps need to be taken to see actual compiler performance gains as it relates to llvm.
Interesting that the two highest functions in that trace relate to Bitcode serialization and GlobalISel. Why is Swift serializing Bitcode if it is also running ISEL in the same compile?
Set these to ~100ms and add a few explicit types whenever they log, and compile time will be significantly improved.
Swift is a slow compiler in exceptional cases, maybe in the 99th percentile, but when you know where this is happening it's fairly trivial to avoid it and keep the compiler nimble and stay productive as an engineer.
In this particular case, adding an explicit type didn’t help at all.
I’ve worked in a large Swift project with those timeout warnings enabled, and didn’t find them too helpful. They showed up a lot and it was rarely obvious how to quickly fix them. Possibly it would have been useful if those warnings were enabled from the start and developers had always fixed them proactively. I’m skeptical that that could work in a big fast-moving team project, though.
I think it's reasonable to have a warnings-are-errors approach even in medium sized teams, or if not quite that approach perhaps a "warnings go in the bug tracker" approach.
I'm far from a Swift expert, but I usually found I could reduce the compilation time with ~10 mins of work. When I turned these on for a small codebase (60k lines, ~50% of an engineer split across 3 people), it only took a few hours to solve all the instances of this, and they were mostly obvious cases where a 100 line function could be split into 3 and solve the issue.
Warnings-as-errors is a great policy in general, but these are non-deterministic warnings based on compile time wall timing. Coworker working on a slower computer than you? He can run into hundreds of errors after a pull that you introduced but couldn't see, what now? Running something intensive in the background? You can't compile successfully any more. Not ideal.
That's true, but a setting of 100ms is a few orders of magnitude higher than expressions should be so it's quite possible to make this fairly reliable.
The problem is that if this ever catches anything, then there's things that it almost catches. And you have no way of telling if you have something expression that takes 99 ms on your machine, so will take 101 ms on your coworkers'. The appropriate way to deal with this in general is to have separate warning and error thresholds, where the difference between the two is greater than the variance between machines, e.g. 50 ms and 100 ms; at least in this case when your coworker checks out a broken build he can see that warnings were introduced in your commit in a CI build log and track it down to the source.
It's easy to have one setting for local development and one for CI. Xcode supports different profiles natively so you can just put 50ms or whatever in your user settings and leave 100ms or even 1s in your standard build settings.
Also, this sort of works the other way too. Just because something failed at the 100ms threshold doesn't mean it would complete in 101ms, it might take 500ms, 10 minutes, or be undecidable, and it's important to fail CI in those sorts of cases to protect other developers from issues, so having a limit is useful even if it's relatively high for the default case.
I guess I don't see what the actual workflow is supposed to be for a new team member who joins (potentially with a different computer than everyone else), checks out the code, and has dozens of errors when they first try to build.
I'm a huge fan of error'ing on warnings, but I really don't see it as appropriate for nondeterministic cases.
I see what you mean, but in this case it's that the Swift compiler is keeping you productive by doing a ton of work for you, and asking for a little clarity every now and again so that it doesn't have to do exhaustive searches of what you mean.
Reminds me a little bit of the time in an intermediate programming course where we had to write a variety of programs and the input was an enormous string containing the entire novel "Jane Eyre."
I discovered very quickly that building a string by concatenating the input one word at a time in Java, my preferred language at that time, was a huge mistake, because of the way string literals work in Java. StringBuilder to the rescue!
At first I was astounded at how slow it was, but when I learned how the underlying language semantics worked, it made sense.
Wow, that's a fantastic exercise for a computer science class. Most classes only work on programs so small that students don't really get a feel for how things scale.
It was one of my more memorable ones. It had ~50 exercises that I recall, and the core of the program was supposed to compute a word frequency list on the words in the novel (with a blacklist of words like common adjectives etc). The twist was you had to do it with a particular programming paradigm in mind each time - functional, object oriented, MVC, REST API, anything you could think of. It was very eye opening solving the exact same problem in so many different ways.
In this case, Swift is being, lets say suboptimal, compiling an 8MB array of integers.
It's worth mentioning another, more common, cause. There can be computational cost explosion when Swift resolves the types for function calls (and operators). You notice it when you get the "too complex" error, but you may be tolerating slow compiles which are just slightly less than too complex. A few explicit type annotations in these situations can do wonders for speeding your development cycle.
Edit: redact
I unfortunately can't tell you how to find these. Maybe a "give it a go with reduced complexity limit" compiler flag would help find them.
My most frustating error with swiftc is the switch on enum "not being able to complete in acceptable time" (or something like that). Basically, when you want an exhaustive check on combination of enums, you're often reaching the limits of the compiler.
something like this
enum State { case A, … }
switch (fromState, toState) { case (.A, .B): … }
in which you want to make sure you didn't forget a combination.
I implemented a prototype version of the algorithm in that paper when exploring exhaustiveness checking for pattern matching in Dart. I found it pretty easy to understand, but also really easy to get it to generate huge combinatorially large spaces. Some careful memoization and deduplication helped, but even so I never got the performance to a state I felt comfortable with.
Instead, I went with Luc Maranget's classic approach and figured out a way to adapt it to a language with subtyping (with a ton of work from Johnni Winther to figure out all of the hard complex cases around generics):
The performance (in the prototype!) was dramatically better. You can always make pattern matching go combinatorial, but I haven't seen any real-world switches get particularly slow with our approach yet, and we have some fairly large tests of matching on tuples of enums.
I've got some bad experience with C++ for this. Yes, part of that was losing the flow, which is probably why way back then scripting languages were easier to sell than these days (where you can throw more money on the issue, with umpteen core dev machines).
But the other part was the reaction amongst developers to this. Layers of abstractions introduced just to please the compiler gods. C++ was particular famous for this, all for the sake of partial compilation still working -- sometimes this had the benefits of decoupling, but often it was a few cake layers on top of that.
So you end up with convenient languages, but have to forsake some of that convenience to actually be productive. But you still had to learn the convenient unused part and know the difference, even if you almost never could afford to use it (if it's just about runtime performance, you often don't need that. But you don't want to cause the projects compilation or test phase to double, just because that functional-combinatoric template metaprogramming approach is so much neater)
Oh man, if only we would have Wirth-dows, not Windows ;)
I’ve been hearing about Jai for, what, maybe close to a decade already?! Is it actually gonna happen? I feel like it’s one of those languages that falls under the category of vaporware.
I don't think calling it Vaporware is accurate, since it's one person's project for their own other projects. There isn't (nor should there) be any expectation on general availability of it.
One thing I hate about LLVM is that once you get into the ecosystem to get “another alternative” or “abandon LLVM” it’s like hell, you get all these nice cool features then implementing a custom compiler for debugging can be much much harder than that
Huge literals do occur in programmatically generated source code. I'm working on a project now where I generate C source code with large, static tables. Paraphs this is not a common scenario for Swift's target use cases.
Rust compiled in about a minute and the runtime was 2 secs
That seems really slow as well! Since reading the same thing in JSON only takes a fraction of a second.
I think it would be well worthwhile speeding up this kind of thing. Obviously a million-element static array isn’t common usage, but if you can find and fix the bottlenecks there, it will help make everyday usage faster too in lots of small ways.
In Rust you can include_bytes! a file if the situation is that you just want a whole bunch of data, as is often the case with practical systems, (the result of the include_bytes! macro is a &'static [u8; N] ie an immutable reference to an array of N unsigned bytes which lives forever). So for example you can write code which cares how long firmware.bin is, without needing to ensure the code is updated appropriately, and yet doesn't bake firmware.bin inside your finished binary, by writing something like const fw_length = include_bytes!("firmware.bin").len(); and the compiler can see we don't actually want the array at runtime, just its length, so the data evaporates from the output software.
Parsing a programming language is just always going to be slower than just reading a file into memory, which is why Rust had include_bytes! from the outset and it's silly that C didn't do likewise even in 1989, let alone C++ in 1998.
This particular exercise wants a machine-word size integer, so in Rust that's isize, but you could isize::from_le_bytes() or whatever with chunked conversions, which will happen at runtime but ought to go very much faster than parsing text.
Okay, but the question I’m asking is, why does the basic version take 60 seconds to compile? Why not 6 seconds, or 60 milliseconds?
If there’s a good and insurmountable reason why it must be a minute and no less, okay. But I’d be amazed if there aren’t some easy wins to be made that could speed it up by a decent amount.
That’s not worth doing just because of the silly million-element array case, of course; but if you can make that silly case faster, lots of more important use cases will get faster too.
I mean, if you feel it's worth figuring this out you totally can, Rust's source code really does successfully download and just build so you can tinker with it, I patched it some weeks back to improve the diagnostics you get for type errors where e.g. you wrote 'X' (a 21-bit integer representing the Unicode Latin capital X) but you ought to have written b'X' (an 8-bit unsigned byte representing the ASCII code for X) - yes numerically those values are identical but Rust correctly does not consider that to mean they're the same type - and it was like an hour's work to build Rust and first figure out where to attempt my changes.
You can submit a PR and after a robot puts it in a pile to be looked at, actual humans will ask you about your proposed change, they can ask other robots to check whether it works OK on the huge piles of real world Rust out there, and so on.
I used to see the compiler run away, consuming all available RAM and CPU, back in the early days of Mac OS X. So this is not necessarily something unique to Swift. Then there was the ill-fated garbage collection system that got bolted onto Objective C. At some point after the compiler got fixed, ARC was added, and the GC was deprecated, in 2012, is the point in time when Cocoa development felt solid (to me).
I don’t know how you’d escape this. Other platforms have their own problems. As far as I can tell, they’re all science projects, because our understanding of language design and library design keeps changing.
At some point after the compiler got fixed, ARC was added, and the GC was deprecated, in 2012, is the point in time when Cocoa development felt solid (to me).
Seconded -- Obj-C was really nice to work with at that time.
I do like a lot of the new stuff in Swift, but in many ways it’s a return to the bad old days in terms of the overall dev experience.
> I do like a lot of the new stuff in Swift, but in many ways it’s a return to the bad old days in terms of the overall dev experience.
It depends on the angle you’re coming from IMO.
For me Swift has been a major improvement overall for a couple reasons: it has a ton of quality of life features that can only be had in Objective-C with a laundry list of CocoaPods, and its type system allows me to do fairly major refactors on a regular basis that I wouldn’t dream of trying with Obj-C. Not having to maintain header files is also a bigger deal than I thought it’d be…
I think a certain amount of ambition is necessary if you’re making a language for general use. You get important feedback from real-world use, and that means you need a population of developers willing to take risks on new languages.
If we go by the Objective C timeline, Swift will be pretty good in the year 2042.
I mean. I love Swift, but it's use case is limited. While there's projects underway (and have been for years) to make it a server-side language for example, I'd never use it for anything but native iOS / Mac code.
For OP's specific case, wouldn't it be better to just special case the compiler to detect that it is a large vector, and pick the same datatype for all elements of the vector? Suddenly an exponential search becomes linear.
You would think the part of the compiler that parses integer literals would assign them the type integer, making this close to a no op for the type checker.
Tangent to this but Swift is slow in surprising ways: json decoding and decoding strings into date objects are insanely slow. Both are common operations for an iOS app (eg client/server app that decodes a json encoded payload) and I have no idea why not solved years ago.
Another surprise is that Apple’s protobuf generator is able to produce iodiomatic Swift, the same is not true for Kotlin :)
I ran into a similar problem in a Fortran code, where a huge array was being assigned a bunch of literals one by one. The optimizing compiler slowed down substantially (though not to this extent), who knows what it hoped to find…
Actually, no. There is no portable "incbin" in C/C++, so one way to bake assets into the .rodata section is to convert a binary file into a huge "const unsigned char data[8192567] = { 0xFF, 0xD8, 0xFF, 0xE0, ... }" string and stash it into a .h or .c file. And yes, people do that often enough that gcc and clang actually had to optimize for this case specifically.