Understand function shaking and function throttling thoroughly

Preface

In our daily development, we often encounter problems with processing high frequency callbacks, such as scrollView DidScroll or textView callbacks when users enter text. In these scenarios, to reduce the CPU burden, we generally use some methods to reduce high frequency calculations. This article describes two ways to solve high frequency calculations: debounce and throttle.

In practice, if the two are mixed up, the user experience will be greatly affected.

1. Anti-shake (debounce)

First, the first idea is to prevent shaking: when the first trigger event is fired, the function is not executed immediately, but a time limit value such as 200ms is given. Then:

  • If the event triggers again within 200ms, the current timer cancels and the timer restarts
  • If the event is not triggered within 200ms, execute the event handler

Effect: Triggers events continuously for a specified time, and executes a processing function only once after a specified time after the last event triggers.

2. throttle

When an event is first triggered, it does not execute the function immediately, but instead gives a duration value such as 200ms, starts timing, and then

  • Within 200ms, all events triggered again are ignored, a function is executed once, and the timer is cleared after the timer is finished.
  • After 200ms, start the cycle again

Effect: If the same event is triggered a large number of times in a short period of time, the function will no longer work for a specified time period after it is executed once, and will not take effect again until that time has elapsed.

The following is an iOS code implementation of throttle that debounce can handle with the same logic:

- (BOOL)performSelector:(SEL)selector withObject:(id)object withMinInterval:(NSTimeInterval)minInterval {
    NSString *selectorStr = NSStringFromSelector(selector) ?: @"";
    BOOL shouldPerfrom;
    @synchronized(self) {
        NSMutableDictionary<NSString *, NSNumber *> *throttleDict = objc_getAssociatedObject(self, kGCThrottleDictKey);
        if (throttleDict == nil) {
            throttleDict = [NSMutableDictionary new];
            objc_setAssociatedObject(self, kGCThrottleDictKey, throttleDict, OBJC_ASSOCIATION_RETAIN);
        }
        NSTimeInterval lastTimeInterval = [[throttleDict objectForKey:selectorStr] doubleValue];
        NSTimeInterval nowInterval = [[NSDate date] timeIntervalSince1970];
        // [CUtility getCurrentClock] takes a little longer.
        //Avoid users changing time [[NSDate date] timeIntervalSince1970];
        shouldPerfrom = lastTimeInterval == 0 || (fabs(nowInterval - lastTimeInterval) > minInterval); // With fabs, avoid changing the time of the user which results in less than
        if (shouldPerfrom) {
            [throttleDict setObject:@(nowInterval) forKey:selectorStr];
        } else {
            MMInfo(@"%@ %@ ignored:%f - %f < %f", object, selectorStr, nowInterval, lastTimeInterval, minInterval);
        }
    }
    if (shouldPerfrom) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
        [self performSelector:selector withObject:object];
#pragma clang diagnostic pop
        return YES;
    } else {
        return NO;
    }
}

3. Summary

Anti-shake and throttling are two solutions to the problem of too high trigger frequency of short-time events. Anti-shake is dynamic and the processing function will not be executed until the last event triggers. No function has been processed in the middle, which prevents the page from dithering. It can be thought that the process is smooth and continuous.

Throttling, on the other hand, is discrete, clearly dividing up time and performing only one event handling function in a specified time block.

4. Think Questions

It seems that both anti-shake and throttling can solve the problem of high frequency callback, so when do we use debounce and when do we use throttle?

First, debounce is certainly better in terms of reducing the frequency of CPU calculations, since it will only trigger once during the entire high frequency callback period.

However, the debounce calculation frequency is low, and the impact is that the user experience may be lost. For example, if debounce is used in scrollViewDidScroll to trigger some rendering logic, if the user is scrolling the screen all the time, the screen will not be rendered until the user releases his hand, and the entire scrolling process will not be loaded.

Considering the CPU computing power is becoming stronger and stronger, we can give priority to the throttle scheme in solving the high frequency callback problem. Combining my own project experience, whether solving the scrollViewDidScroll problem or the textViewDidChange problem, is a throttle-first solution.

5. Disadvantages of using debounce/throttle

Whether it's debounce or throttle, we understand that their main application is to make calls less frequent.

Both of these, however, are calls that discard the caller.

Let's think about what debounce did?

The logic of debounce is that when there is a method call, I delay n seconds to execute it, and if there are method calls in n seconds, cancel the last delay, and re-delay n seconds to execute the method.

But we can imagine that if an object calls this method constantly, it will be delay ed all the time, in the extreme case it will never execute, which will result in a bug.

And then let's think about what throttle did?

The logic of throttle is that the method is executed only once in n seconds, and if there are method calls in n seconds, the method is discarded directly.

The problem then arises, in general the object that calls this method after holding the latest state, while throttle's logic causes the old state to be executed first and the new state to be discarded.

6. How to choose debounce/throttle

debounce executes the latest status but risks being delay ed all the time.

throttle performs a relatively old state but risks losing the latest state.

So if your method of using delay (debounce/throttle) dispersion is state independent, such as UI click events only, then throttle is recommended.

If your method of using delay (debounce/throttle) dispersion is state dependent, there are two scenarios:

  • If the high frequency call duration is very long, throttle is recommended, but with as little interval as possible, such as 0.2 seconds
  • debounce can be used if the high frequency calls do not last long and are frequently invoked during the transient time period (e.g. when the interface has just been opened and various cgi backpacks are refreshed)

I'm a new programmer. The more you know, the more you don't know. Thank you all for your compliments, collections and comments. I'll see you next time!

Tags: Design Pattern

Posted on Tue, 26 Oct 2021 13:19:26 -0400 by devarmagan