I have a code, that copies integers to buffer1, then from buffer1 to buffer2 and then consumes all data from buffer2.
It processes 1000 values in 15 seconds, which is a lot of time compared to size of input. When I remove the " Task.Delay(1).Wait() " from the second task t2, it completes quite fast.
Now, my question is: is the slowdown because of two threads competing for the lock or is my code somehow faulty?
var source = Enumerable.Range(0, 1000).ToList();
var buffer1 = new BlockingCollection<int>(100);
var buffer2 = new BlockingCollection<int>(100);
var t1 = Task.Run
(
delegate
{
foreach (var i in source)
{
buffer1.Add(i);
}
buffer1.CompleteAdding();
}
).ConfigureAwait(false);
var t2 = Task.Run
(
delegate
{
foreach (var i in buffer1.GetConsumingEnumerable())
{
buffer2.Add(i);
//Task.Delay(1).Wait();
}
buffer2.CompleteAdding();
}
).ConfigureAwait(false);
CollectionAssert.AreEqual(source.ToList(), buffer2.GetConsumingEnumerable().ToList());
An update: this is just a demo code, I am blocking for 1 milisecond just to simulate some computations that take place in my real code. I put 1 milisecond there because it's such a small amount. I cannot believe that removing it makes the code complete almost immediately.
The clock has ~15ms resolution. 1ms is rounded up to 15. That's why 1000 items take ~15 seconds. (Actually, I'm surprised. On average each wait should take about 7.5ms. Anyway.)
Simulating work with sleep is a common mistake.
Related
I'm trying to add the numbers in a range eg. 1 to 50000000. But using a for-loop or reduce(_:_:) is taking too long to calculate the result.
func add(low: Int, high: Int) -> Int {
return (low...high).reduce(0, +)
}
Is there any way to do it using multiple threads?
Adding a series of integers does not amount to enough work to justify multiple threads. While this admittedly took 28 seconds on a debug build on my computer, in an optimized, release build, the single-threaded approach took milliseconds.
So, when testing performance, make sure to use an optimized “Release” build in your scheme settings (and/or manually change the optimization settings in your target’s build settings).
But, let us set this aside for a second and assume that you really were doing a calculation that was complex enough to justify running it on multiple threads. In that case, the simplest approach would be to just dispatch the calculation to another thread, and perhaps dispatch the results back to the main thread:
func add(low: Int, high: Int, completion: #escaping (Int) -> Void) {
DispatchQueue.global().async {
let result = (low...high).reduce(0, +)
DispatchQueue.main.async {
completion(result)
}
}
}
And you'd use it like so:
add(low: 0, high: 50_000_000) { result in
// use `result` here
self.label.text = "\(result)"
}
// but not here, because the above runs asynchronously
That will ensure that the main thread is not blocked while the calculation is being done. Again, in this example, adding 50 million integers on a release build may not even require this, but the general idea is to make sure that anything that takes more than a few milliseconds is moved off the main thread.
Now, if the computation was significantly more complicated, one might use concurrentPerform, which is like a for loop, but each iteration runs in parallel. You might think you could just dispatch each calculation to a concurrent queue using async, but that can easily exhaust the limited number of worker threads (called “thread explosion”, which can lead to locks and/or deadlocks). So we reach for concurrentPerform to perform calculations in parallel, but to constrain the number of concurrent threads to the capabilities of the device in question (namely, how many cores the CPU has).
Let’s consider this simple attempt to calculate the sum in parallel. This is inefficient, but we’ll refine it later:
func add(low: Int, high: Int, completion: #escaping (Int) -> Void) {
DispatchQueue.global().async {
let lock = NSLock()
var sum = 0
// the `concurrentPerform` below is equivalent to
//
// for iteration in 0 ... (high - low) { ... }
//
// but the iterations run in parallel
DispatchQueue.concurrentPerform(iterations: high - low + 1) { iteration in
// do some calculation in parallel
let value = iteration + low
// synchronize the update of the shared resource
lock.synchronized {
sum += value
}
}
// call completion handler with the result
DispatchQueue.main.async {
completion(sum)
}
}
}
Note, because we have multiple threads adding values, we must synchronize the interaction with sum to ensure thread-safety. In this case, I'm using NSLock and this routine (because introducing a GCD serial queue and/or using reader-writer in these massively parallelized scenarios is even slower):
extension NSLocking {
func synchronized<T>(block: () throws -> T) rethrows -> T {
lock()
defer { unlock() }
return try block()
}
}
Above, I wanted to show the above simple use of concurrentPerform but you are going to find that that is much slower than the single threaded implementation. That is because there is not enough work running on each thread and we’ll do 50m synchronizations. So we might, instead, “stride” adding a million values per thread:
func add(low: Int, high: Int, completion: #escaping (Int) -> Void) {
DispatchQueue.global().async {
let stride = 1_000_000
let iterations = (high - low) / stride + 1
let lock = NSLock()
var sum = 0
DispatchQueue.concurrentPerform(iterations: iterations) { iteration in
let start = iteration * stride + low
let end = min(start + stride - 1, high)
let subtotal = (start...end).reduce(0, +)
lock.synchronized {
sum += subtotal
}
}
DispatchQueue.main.async {
completion(sum)
}
}
}
So, each thread adds up to 1 million values in a local subtotal and then when that calculation is done, it synchronizes the update of sum. This increases the work per thread and dramatically reduces the number of synchronizations. Frankly, adding a million integers is still no where near enough to justify the multithreading overhead, but it illustrates the idea.
If you want to see an example where concurrentPerform might be useful, consider this example, where we are calculating the Mandelbrot set, where each pixel of the calculation might be computationally intense. And we again stride (e.g. each iteration calculates a row of pixels), which (a) ensures that each thread is doing enough work to justify the multithreading overhead, and (b) avoid memory contention issues (a.k.a. “cache sloshing”).
If you want a function to just return the sum to all integers in range from low to high then you can do it even faster with some simple maths
you can consider an arithmetic sequence starting from low and going to high with a common difference of 1 ,and have (high - low + 1) elements in it.
then the sum will straight up be :-
sum = ( (high * ( high + 1 )) - ((low * (low - 1)) ) / 2
I am creating a complex metronome app using Ionic and Web Audio API.
At certain points, the metronome could be playing 10+ 'beats' a second.
This essentially calls this function 10 times+ a second.
function playSound(e, name) {
var buffer = audioBuffers[name];
var source = audioContext.createBufferSource();
var gain = audioContext.createGain();
source.connect(gain);
gain.connect(audioContext.destination);
gain.gain.value = 1;
source.buffer = buffer;
source.connect(audioContext.destination);
sched.nextTick(e.playbackTime, () => {
source.start(0);
});
}
The user can choose multiple samples, so I fetch them all first once and store the buffer in an array to improve performance instead of making a XMLHttpRequest() every time.
The issue is that when playing at these higher rates the playback gets odd and sometimes goes out of sync. I am using https://github.com/mohayonao/web-audio-scheduler which works lovely so I know its not a timing issue.
If I swap out the sample playback for a basic oscillator:
function oscillator(e) {
const t0 = e.playbackTime;
const t1 = t0 + 0.4;
const osc = audioContext.createOscillator();
const amp = audioContext.createGain();
osc.frequency.value = 1000;
osc.start(t0);
osc.stop(t1);
osc.connect(amp);
amp.gain.setValueAtTime(1, t0);
amp.gain.exponentialRampToValueAtTime(1e-6, t1);
amp.connect(masterGain);
sched.nextTick(t1, () => {
osc.disconnect();
amp.disconnect();
});
}
Performance is fine no matter what tempo. Is there any improvements I can make to the sample playback to help improve performance?
Your first function just uses source.start(0); which makes me think you're relying on setTimeout or setInterval to "schedule" the audio. The second one properly uses the Web Audio scheduler ("start(t0)"). See "A Tale of Two Clocks" for more: https://www.html5rocks.com/en/tutorials/audio/scheduling/.
What cwilso says is right. Use AudioContext.CurrentTime and +/- to determine the next time for setTimeout manually and not with that scheduler library. Then everything should be fine.
I'd like my test to fail if it runs slower than 0.5 seconds but the average time is merely printed in the console and I cannot find a way to access it. Is there a way to access this data?
Code
//Measures the time it takes to parse the participant codes from the first 100 events in our test data.
func testParticipantCodeParsingPerformance()
{
var increment = 0
self.measureBlock
{
increment = 0
while increment < 100
{
Parser.parseParticipantCode(self.fields[increment], hostCodes: MasterCalendarArray.getHostCodeArray()[increment])
increment++
}
}
print("Events measured: \(increment)")
}
Test Data
[Tests.ParserTest testParticipantCodeParsingPerformance]' measured [Time, seconds] average: 0.203, relative standard deviation: 19.951%, values: [0.186405, 0.182292, 0.179966, 0.177797, 0.175820, 0.205763, 0.315636, 0.223014, 0.200362, 0.178165]
You need to set a baseline for your performance test. Head to the Report Navigator:
and select your recent test run. You'll see a list of all your tests, but the performance ones will have times associated with them. Click the time to bring up the Performance Result popover:
The "Baseline" value is what you're looking for--set it to 0.5s and that will inform Xcode that this test should complete in half a second. If your test is more than 10% slower than the baseline, it'll fail!
The only way to do something similar to what you describe is setting a time limit graphically like #andyvn22 recommends.
But, if you want to do it completely in code, the only thing you can do is extend XCTestCase with a new method that measure the execution time of the closure and returns it to be used in an assertiong, here is an example of what you could do:
extension XCTestCase{
/// Executes the block and return the execution time in millis
public func timeBlock(closure: ()->()) -> Int{
var info = mach_timebase_info(numer: 0, denom: 0)
mach_timebase_info(&info)
let begin = mach_absolute_time()
closure()
let diff = Double(mach_absolute_time() - begin) * Double(info.numer) / Double(1_000_000 * info.denom)
return Int(diff)
}
}
And use it with:
func testExample() {
XCTAssertTrue( 500 < self.timeBlock{
doSomethingLong()
})
}
I'm writing a streaming Twitter client that simply throws the stream up onto a tv. I'm observing the stream with RxJava.
When the stream comes in a burst, I want to buffer it and slow it down so that each tweet is displayed for at least 6 seconds. Then during the quiet times, any buffer that's been built up will gradually empty itself out by pulling the head of the queue, one tweet every 6 seconds. If a new tweet comes in and faces an empty queue (but >6s after the last was displayed), I want it to be displayed immediately.
I imagine the stream looking like that described here:
Raw: --oooo--------------ooooo-----oo----------------ooo|
Buffered: --o--o--o--o--------o--o--o--o--o--o--o---------o--o--o|
And I understand that the question posed there has a solution. But I just can't wrap my head around its answer. Here is my solution:
myObservable
.concatMap(new Func1<Long, Observable<Long>>() {
#Override
public Observable<Long> call(Long l) {
return Observable.concat(
Observable.just(l),
Observable.<Long>empty().delay(6, TimeUnit.SECONDS)
);
}
})
.subscribe(...);
So, my question is: Is this too naïve of an approach? Where is the buffering/backpressure happening? Is there a better solution?
Looks like you want to delay a message if it came too soon relative to the previous message. You have to track the last target emission time and schedule a new emission after it:
public class SpanOutV2 {
public static void main(String[] args) {
Observable<Integer> source = Observable.just(0, 5, 13)
.concatMapEager(v -> Observable.just(v).delay(v, TimeUnit.SECONDS));
long minSpan = 6;
TimeUnit unit = TimeUnit.SECONDS;
Scheduler scheduler = Schedulers.computation();
long minSpanMillis = TimeUnit.MILLISECONDS.convert(minSpan, unit);
Observable.defer(() -> {
AtomicLong lastEmission = new AtomicLong();
return source
.concatMapEager(v -> {
long now = scheduler.now();
long emission = lastEmission.get();
if (emission + minSpanMillis > now) {
lastEmission.set(emission + minSpanMillis);
return Observable.just(v).delay(emission + minSpanMillis - now, TimeUnit.MILLISECONDS);
}
lastEmission.set(now);
return Observable.just(v);
});
})
.timeInterval()
.toBlocking()
.subscribe(System.out::println);
}
}
Here, the source is delayed by the number of seconds relative to the start of the problem. 0 should arrive immediately, 5 should arrive # T = 6 seconds and 13 should arrive # T = 13. concatMapEager makes sure the order and timing is kept. Since only standard operators are in use, backpressure and unsubscription composes naturally.
The Timer always jumps right into the function instead of waiting 2000 milliseconds
stop();
var hey:Number= 2000;
function exercise(frame) {
trace("sup");
this.gotoAndStop(frame);
}
setTimeout(exercise(2),hey);
trace(hey);
The setTimeout function (ie. not a Timer) takes the following parameters:
public function setTimeout(closure:Function, delay:Number, ... arguments):uint
Try:
// Save the return value so you can clear it later, otherwise your app for leak memory
var myTimeOut:uint = setTimeout(exercise, hey, 2);
trace(hey);
// the timeout closure object will not be garage collected if you do not clear it
clearTimeout(myTimeOut);
If you are doing this over and over, consider creating a Timer and setting/reseting the repeat count to 1 and then you can just restart the timer when need.