State of String: ABI, Performance, Ergonomics, and You!

For a more general solution, I think a `var numericValue: Int? { get }` on Character would make sense. Unicode defines (at least one) semantics for this and ICU provides this functionality already.

Minor style point – this should be a failable init on Int rather than a computed property on Character

i.e. Int.init?(_: Character), matching Int.init?(_: String, radix: Int = 10), only it doesn’t need the radix arg cos it’s only a single character.

`Int.init?` is probably better, yes. If you wanted to take that to its logical conclusion, that would include `Double.init?` for mathematical constants and some `Rational.init` for fraction graphemes. These are pretty far off the deep end ;-)

Minor logic point – radix isn't important, but that's not because it’s only a single character but rather because of how Unicode(/ICU) defines character properties. Numbers can be much higher than 9, e.g. 万 is ten-thousand. Even worse is cuneiform, which is sexagesimal (and of course cuneiform is properly encoded in Unicode!). Luckily, Unicode’s numeric values are always presented in base-10, AFAICT.

Seems like two different features got tangled here, both of which are useful…

On the one hand, Chris’ case of “I’ve got a Character and I want to convert ASCII 0-9a-f hex into an Int and it’s incredibly painful in Swift 4”. This is a pretty common everyday need and calls for a failable initializer on Int, similar to the one that takes a String, that is very fast and only covers those characters. Thinking about it, you probably do want an optional radix argument, because you want to explicitly control whether “a” is nil or 10.

Separate different feature is exposing Unicode properties of characters, including things like :five: = 5.0, ½ = 0.5 etc, but where a is nil. This possibly works better as a property like numericValue: Double? than an init on Double.


On Jan 11, 2018, at 17:34, Michael Ilseman <> wrote:

On Jan 11, 2018, at 4:20 PM, Ben Cohen < <>> wrote:

On Jan 11, 2018, at 12:32 PM, Michael Ilseman via swift-dev < <>> wrote:

If implemented inside the std lib, it can still access character’s internals, which is a reasonable thing to do for something so performance-sensitive.

@Michael_Ilseman Now that Discourse is here, perhaps you could split the various parts of this discussion into threads so we can discuss them separately? That would make it much easier to follow each separate thread.


What is the expected existential inline buffer size going to wind up being? We sized it to 3 words specifically to fit string and array. It would be great to shrink that to 2 or 1 words.

I started discussion on the issue of determining the size of the inline storage here:

Thanks Arnold. In the absence of useful empirical data, you should go with analytic data. In Swift 1 we set the existential size to be big enough to hold string and array instances. Slice instances are not important to size to in this case IMO.

Getting it down to a 2 word existential buffer will be a big improvement.

@Chris: You state that it will be a big improvement, but it isn't at all obvious to me that will be true. There is a clear tradeoff. A 2-word buffer will result in minor improvements for 1-2 word types and major regressions for 3-word types. You have to win a LOT on the 1-2 word types to overcome any regressions for 3-word types that no longer fit.

There is more than just the stdlib data types involved here. We've been advising performance-hungry Swift developers to tune their code to work well with 3-word inline buffers. If we drop it to 2 words, some fraction of user code will be impacted. We have no good way to know what that fraction would be.

Longer term, I think it would be really nice to be able to tune the buffer size for specific existential types where we have enough knowledge about conforming types and use cases to make that a sensible thing to do. Perhaps the optimizer could even tune the buffer size automatically when all possible conforming types are statically knowable.

I hope this future direction is being considered while making decisions about ABI.

That's a good idea for potential internal optimizations, but I don't think it affects ABI. IIUC, ABI would dictate the behavior surrounding public interfaces, which cannot see all use cases, and thus this optimization is never applicable.

The good news is that internal interfaces can always be optimized, de-virtualized, specialized, and have this sort of optimization applied. I don't know how a theoretical multi-module optimization approach could pan out, but this might be an idea for that too.

One interesting question here is how would that work. Obviously you don't want to copy from the inline buffer into a new heap object. I wonder if we could have a way to treat the box with the larger inline buffer as the heap pointer, preventing reallocation. Just a random thought.

That is a great idea. Do you know how to split an existing thread, or should I start new ones?

I don’t think you can split, but an admin might (@Nicole_Jacque ?) (don’t know if Discourse allows it at all). Otherwise, new threads with links to the originals would work.

I can split -- let me test whether or not non-admins can too.

Ok, looks like it's admin only. Do you want me to split this from Michael Gottesman's post?

If possible, I’d say splitting the regex discussion into a thread and the raw string discussion into another. Then @Michael_Ilseman can create threads about the other topics mentioned, like missing API.

Ah, that looks a little complicated and it might be better to just spin off new threads. @Jon_Shier, do you think you have enough of an idea on what to do for Character properties to start a pitch on evolution? Alternatively, I could start a more open-ended preliminary discussion with some guidance.

More details of String's ABI settle by the day, so it might make sense to continue that avenue of discussion in a new thread anyways. This is likely a prerequisite for the performance additions like a theoretical UnmanagedString and exposed performance flags anyways.

@anandabits has already started a discussion about the generalization of pattern matching at Generalized pattern matching - #2 by AlexanderM. It might make sense to pile on there for that.

For regular expressions, there's still a lot of syntactic holes in the straw-man. A new thread with a new straw-man to be knocked down is in order. As immediate focus is on ABI, it would be a slow-moving thread, but I think it would make sense to start in a week or two.

I do want to begin the discussion (perhaps on "development" at first) about the String interpolation performance issues. @beccadax, are you interested in starting that? Do you want any help or preliminary discussion prior?

In a few weeks, I'd like to solicit thoughts on a "String Dojo" in the Using Swift category.

I'm happy to take a stab at the UnicodeScalar/Character properties pitch based on my ICU bindings as well, if @Jon_Shier would like. (I haven't had the time I wanted to put into a thoughtful opening post, and I still haven't wedged Discourse into my daily workflow yet, but maybe a brief post to kick off discussion is still in order.)


Doh! I totally meant @allevato instead of @Jon_Shier ! (This certainly demonstrates the need for threading!).

@allevato, please proceed!

Finally had a chance to jot down a pitch—it can be found here.

I completely agree with @anandabits here. There is nothing in the ABI that seems like it would preclude adding the ability to say (making up syntax, which is obviously wrong):

var x : SomeExistentialType$32

and have it reserve space for 32 inline words of storage. This really does seem like the right long term answer: the Swift compiler should pick a right default size that is small (2 words would be great :-), and then allow specific authors to pick the existential size that makes sense for their app. This is often goodness because Swift programmers often have a good idea of what sorts of types will conform to their protocols, particularly if building an app or something else where there is a closed set of expected types that conform to a protocol.


I hadn't been thinking of tuning the buffer for individual properties (although that might be useful as well). I was thinking of tuning the buffer size for all existential values of a specific protocol (or at least tuning the default if a property-level override was also supported).

Good point, that would also make a lot of sense.