Optimizations made Automatically after Multiple Runs

I have designed a simple ascii string and tested it as such:

measure {
  var string: ASCII = "Hello, world"!
  string.count
  for char in string {
    print(char.byteValue)
  }
}

measure {
  var string = "Hello, world"!
  string.count
  for char in string {
    print(char)
  }
}

Part 1:
The first run, both take about 0.01 seconds. The second run, the standard string increases to 0.001, and by the third run it is around 0.0005. I have noticed this many times, where after many runs the native types get faster and custom types get slower.

Is the compiler performing optimizations for the standard types that it cannot perform on custom types. Are there any attributes that can be applied to my custom type for improved speed?

Part 2:
I also want to know what all the @sil, @inline(), etc do. I think that @inline(__always) means that the property only depends on other properties and is computed.

Thanks for help!

It's hard to say without more information about how you're compiling and executing when you measure. These particular blocks you pasted are doing a vanishingly tiny amount of work so any measurement you get is going to be very noisy too. It is true in general that, if you run the exact same small program multiple times in a row, then successive executions will benefit from caching at many different levels of the system, from the operating system's virtual memory and disk cache all the way down to the CPU caches, independent of anything a language or particular program does.

2 Likes

The thing is that every run after the third run provides the same as the third run. I put this in a loop and reduced for the mean and it was almost the same.

if you run the exact same small program multiple times in a row, then successive executions will benefit from caching at many different levels of the system

Why is this optimization not applied to custom types?

I don't know what you're seeing based on the example you posted. Can you post a complete example, and describe how you're building and executing your program? Some possibilities include:

  • If you haven't enabled optimization when building your program, then the ASCII type's code will not be optimized, whereas most of String's implementation is in the standard library binary and already optimized.

  • This is a tiny amount of work you're measuring in these blocks, and it's likely that more of what you're measuring is the implementation of the measure function than the code in the block you're passing. How is measure implemented? If it's triggering initialization of parts of the Swift runtime, it may be that the first invocation of measure is picking up that one-time cost, which will only be paid on the first invocation. What happens if you run the measure blocks in opposite order?

In general, benchmarking things correctly is difficult, and it's hard to make conclusions about performance based on small examples like this.

import CoreFoundation
func measure(block: () -> ()) -> CFAbsoluteTime {
  let start = CFAbsoluteTimeGetCurrent()
  block()
  let end = CFAbsoluteTimeGetCurrent()
  return end - start
}

// etc...

The thing is that I see this pattern every time I test in Playgrounds.

If this is unanswerable, then maybe you can help me with Part 2 of my question.

Playgrounds run in a special execution environment where optimization is disabled, and furthermore, every line of code is instrumented to collect logging information and transmit it to the IDE to present in the playground environment. That overhead is going to impact the performance of all code written within the playground document. I don't know the specifics of the playground implementation, but it's entirely possible that there is some one-time initialization that would cause the first measure to always be slower than subsequent ones.

3 Likes