Performance annotations

No, that should be banned. If you aren’t dead-certain, don’t say it won’t lock.

And yes, this does mean most functions won’t be able to use it. That is for the best.

It seems to me that there are two issues here: (1) preventing implicit/unexpected allocations/locks within the body of a function; (2) preventing any allocations/locks as a form of API (e.g. so that someone attempting #1 can perform their task well).

As someone who spends an unfortunate amount of time writing audio processing code (both realtime + offline), I’m much more interested in #1, though I can definitely understand the desire for #2 from those involved in writing libraries & exposing stable APIs.

I would expect the documentation of @noLocks functions to specify (in big, bold font) if there are actually situations in which the function may lock/allocate. Since the function calls are spelled out in source code in my function, that’s satisfying enough for me. So for my use case, I don’t mind if a function may actually allocate under some specific situations as long as that fits my needs. An example:

/// Allocates the first time it’s called, then never again. @noAllocations
/// is only used to enforce the writer’s expectations in the body of the function.
@noAllocations
func doProcessing(on batch: Batch) {
	if !setUpCompleted { 
		unsafePerformance { doAllocatingSetup() }
	}
	// do work fast, absolutely no locks or allocations
}

func processOffline() {
	for batch in data.batched() {
		// I don’t care that this allocates the first time it’s run, I just care that the work
		// happens without allocating on *most* iterations
		doProcessing(on: batch) 
	}
}

So in summary I like the proposal as-is. It seems to optimize for issue #1 as opposed to issue #2. The spelling of the annotations does seem to suggest they do #2, but I think it’s still a reasonable spelling & is learnable. The name unsafePerformance is imperfect, but roughly gets the point across. The name withoutActuallyAllocating implies to me a rule (also suggested above) that allocating inside it is strictly banned — I don’t like this rule & would prefer that it be explicitly allowed to allocate inside of the escape hatch.

With regard to C interop, has there been any thought to how C functions will import? Will they be @noLocks or not?

If they import as plain functions (lacking @noLocks), it would be nice to have a header attribute which would import the function as noLocks or noAllocations? In my imagination, this attribute wouldn’t do any checking of the body of the function (seems hard) — just be a promise that this function obeys the rules. I ask because I can imagine a situation like so:

/// There are no situations where locking inside of `bar` is acceptable.
@noLocks
func bar() {
	unsafePerformance {
		// I need to call this C function which doesn’t lock, but it’s not @noLocks
		// because it’s been imported. I mean only to say that calling 
		// `cFunctionThatDoesNotLock` can be ignored by performance diagnostics,
		// not that the possible implicit lock on `globalVariable` can be ignored.
		cFunctionThatDoesNotLock(globalVariable)
	}
}

If we had a header annotation that meant “I promise there are no locks in here” then I could avoid the whole unsafePerformance block in bar and be prevented from an accidental implicit lock when accessing globalVariable.

This example could also apply to un-annotated Swift functions imported from a library, but at least there's a way to spell the annotation in Swift. I care about the C-interop specifically because today's Swift programs which need have critical sections requiring these performance annotations basically must use C interop to achieve them. So the transition from C libraries today to Swift libraries tomorrow requires a gradual Swift rewrite, using lots of interop along the way.

I guess something like

__attribute__ ((noLocks)) void memmove(void*, const void*, size_t)

will do. Totally ignored in C-land (initially), just for Swift interop.

The existing syntax for this is __attribute__((swift_attr("@noLocks"))). You can of course wrap that in a preprocessor macro that’s only defined when the SWIFT preprocessor macro is defined.

2 Likes
Terms of Service

Privacy Policy

Cookie Policy