Is calling C functions from Swift costly?

Let's say I have some tree data structure to contain strings which is in C. Will calling the C functions with Swift String and then calling another function to lookup for a string going to be costly in terms of bridging? Should I write this as struct in Swift itself? Which approach is better?

How about Objective-C then? It can call C code without any overhead. Is calling C from Swift the same as calling Objective-C from Swift?

Thanks.

Getting a C string from a Swift string is pretty efficient, I think.

In principle, I'd say doing it in pure Swift ought to be faster. But you may be able to do clever things with your data structure that Swift isn't going to do for you, and there's no substitute for testing.

As far as bridging goes, yes.

1 Like

You may want to use withCString to get a scoped C string from the Swift String rather than relying on the automatic bridging. Doing so can save a malloc.

1 Like

… unless you actually need to escape the C string, of course

1 Like

Swift's String is not designed to support lightweight wrapping of other strings. As a general guideline I avoid conversions between String and any other form unless strictly necessary.

Converting between Swift Strings and C strings is usually inefficient (in at least the sense of copying the whole string) because:

  • It always requires copying the string when importing into Swift.

    This requires at least one heap allocation (malloc call) if the string is not trivially short (15 bytes or less… bytes, not characters). For such short strings it may be possible to avoid heap allocations, depending on how the String itself is used. But there's still a memcpy at least.

  • Exporting strings from Swift can be efficient, but only if:

    1. You don't need to "escape" (persist) the string to the C side.

    2. The String happens to be in a suitable form already, which relies on various poorly-defined prerequisites including but not limited to:

      • The desired output encoding being UTF-8.
      • The string not being too short (funnily enough).
      • How exactly the String was created to begin with (which is a whole subset of conditions and prerequisites that are too difficult to even pin down).

    That's a lot of asterisks, which in practice usually amount to it not being efficient, in my experience.

    Even if you test it empirically at one time and everything seems fast, it's possible that your strings will change in some subtle way in future in a way that knocks you off the fast path. Or that Swift's String implementation will change between Swift versions. So in a nutshell I wouldn't rely on it.

NSString

You can potentially get better performance with NSString (whether used via Objective-C or Swift; that part doesn't matter) since it allows you to do things like wrap existing strings, without additional memory copies (e.g. initWithBytesNoCopy:length:encoding:freeWhenDone:).

But, NSString is always heap-allocated itself, so that offsets some of your gains, and there's likely still some overhead from it validating the input bytes. And you'll have to pay for message-sending overheads, which may be worse than the string copy if your strings are short. You can use IMP caching to offset some of those messaging overheads.

So it's unclear to me whether NSString would ultimately come out ahead - unless your median string size is many tens of bytes [or more] - and it probably varies between usage patterns.

Bridging strings in general

In abstract it's hard to say whether you should avoid the language bridging in this case, because it depends on how often you move between them. It's quite possible, for a given use case and performance requirements, that the memory copies and overall inefficiencies won't be a deal-breaker.

But in lieu of more context-specific details - or if you can't predict how your usage might evolve in future - I would avoid bridging the languages in a situation like this; either keep it all in C, or rewrite it all in Swift. Note though that you'll probably lose some performance in translating your tree to Swift, due to Swift's various inefficiencies and optimisation misses.

Correctness

A benefit of doing it in Swift, though, is that you'll get better string handling (in terms of correctness, both technical and natural). e.g. "café" correctly being considered the same as "café".

It's possible to handle Unicode correctly in C but practically nobody does it. Swift isn't perfect but is still far better than C's typical string libraries.

Reliably efficient alternative: raw bytes

If you explicitly don't want any kind of 'correct' string handling - if you just want to treat the "strings" as arbitrary sequences of bytes - then you can just use [UInt8] or UnsafeBufferPointer<UInt8> and reliably have better performance.

The APIs are more limited, then, since you lose access to a lot of string-specific utility methods, but you still have the general Sequence & Collection APIs and the like. With those you can generally reinvent any missing APIs, from String.

4 Likes