Null-free "maps": Is the callback solution slower than tryGet ()?

In the comments to "How to implement a list, set and map in a zero design?" , Steven Judges , and I discussed the use of callbacks with handlers for “found” and “not found” situations, rather than using a method tryGet()using a parameter outand returning a boolean value indicating whether the out parameter was populated. Stephen argued that the callback approach was more complex and almost certainly was slower; I argued that the complexity is small and the performance is the same in the worst case.

But the code speaks louder than words, so I thought that I would implement both and see what I have. The initial question was rather theoretical regarding the language ("And for the sake of argument, let's say this language doesn’t even have it null"). I used Java here because it was what I was comfortable with. Java has no parameters, but it also does not have first-class functions, so in style they should suck the same for both approaches.

(Digression: as far as complexity: I like the callback design because it essentially forces the API user to handle both cases, while the design tryGet()requires the callers to do their own conditional check on the template, which they might forget or make a mistake. But now when I implemented both options, I see why the design tryGet()looks simpler, at least in the short term.)

First, a callback example:

class CallbackMap<K, V> {
    private final Map<K, V> backingMap;

    public CallbackMap(Map<K, V> backingMap) {
        this.backingMap = backingMap;
    }

    void lookup(K key, Callback<K, V> handler) {
        V val = backingMap.get(key);
        if (val == null) {
            handler.handleMissing(key);
        } else {
            handler.handleFound(key, val);
        }
    }
}

interface Callback<K, V> {
    void handleFound(K key, V value);
    void handleMissing(K key);
}

class CallbackExample {
    private final Map<String, String> map;
    private final List<String> found;
    private final List<String> missing;
    private Callback<String, String> handler;

    public CallbackExample(Map<String, String> map) {
        this.map = map;
        found = new ArrayList<String>(map.size());
        missing = new ArrayList<String>(map.size());
        handler = new Callback<String, String>() {
            public void handleFound(String key, String value) {
                found.add(key + ": " + value);
            }

            public void handleMissing(String key) {
                missing.add(key);
            }
        };
    }

    void test() {
        CallbackMap<String, String> cbMap = new CallbackMap<String, String>(map);
        for (int i = 0, count = map.size(); i < count; i++) {
            String key = "key" + i;
            cbMap.lookup(key, handler);
        }
        System.out.println(found.size() + " found");
        System.out.println(missing.size() + " missing");
    }
}

Now an example tryGet()- as far as I understand the pattern (and maybe I'm wrong):

class TryGetMap<K, V> {
    private final Map<K, V> backingMap;

    public TryGetMap(Map<K, V> backingMap) {
        this.backingMap = backingMap;
    }

    boolean tryGet(K key, OutParameter<V> valueParam) {
        V val = backingMap.get(key);
        if (val == null) {
            return false;
        }
        valueParam.value = val;
        return true;
    }
}

class OutParameter<V> {
    V value;
}

class TryGetExample {
    private final Map<String, String> map;
    private final List<String> found;
    private final List<String> missing;

    private final OutParameter<String> out = new OutParameter<String>();

    public TryGetExample(Map<String, String> map) {
        this.map = map;
        found = new ArrayList<String>(map.size());
        missing = new ArrayList<String>(map.size());
    }

    void test() {
        TryGetMap<String, String> tgMap = new TryGetMap<String, String>(map);

        for (int i = 0, count = map.size(); i < count; i++) {
            String key = "key" + i;
            if (tgMap.tryGet(key, out)) {
                found.add(key + ": " + out.value);
            } else {
                missing.add(key);
            }
        }
        System.out.println(found.size() + " found");
        System.out.println(missing.size() + " missing");
    }
}

And finally, a performance test code:

public static void main(String[] args) {
    int size = 200000;
    Map<String, String> map = new HashMap<String, String>();
    for (int i = 0; i < size; i++) {
        String val = (i % 5 == 0) ? null : "value" + i;
        map.put("key" + i, val);
    }

    long totalCallback = 0;
    long totalTryGet = 0;

    int iterations = 20;
    for (int i = 0; i < iterations; i++) {
        {
            TryGetExample tryGet = new TryGetExample(map);
            long tryGetStart = System.currentTimeMillis();
            tryGet.test();
            totalTryGet += (System.currentTimeMillis() - tryGetStart);
        }
        System.gc();

        {
            CallbackExample callback = new CallbackExample(map);
            long callbackStart = System.currentTimeMillis();
            callback.test();
            totalCallback += (System.currentTimeMillis() - callbackStart);
        }
        System.gc();
    }

    System.out.println("Avg. callback: " + (totalCallback / iterations));
    System.out.println("Avg. tryGet(): " + (totalTryGet / iterations));
}

From my first attempt, I got 50% worse performance for the callback than for tryGet()what really surprised me. But judging by my guesses, I added garbage collection, and the execution penalty disappeared.

, , , , .. . , tryGet(). ?


: , TryGetExample OutParameter.

+2
2

, , , . #, Java . - . , , . :

  • TryGet, .
  • TryGet, , , . , v = map[k] v null, , . , (_map.TryGetValue(key, out value)) (_map.TryGetValue(key, out value) && value != null)), , .
  • . , , -, . . , Lookup TryGet .
  • Lookup , .

, , :

using System;
using System.Collections.Generic;
using System.Linq;

namespace ConsoleApplication1
{
    static class CallbackDictionary
    {
        public static void Lookup<K, V>(this Dictionary<K, V> map, K key, Action<K, V> found, Action<K> missed)
        {
            V v;
            if (map.TryGetValue(key, out v))
                found(key, v);
            else
                missed(key);
        }
    }

    class TryGetExample
    {
        private Dictionary<string, string> _map;
        private List<string> _found;
        private List<string> _missing;

        public TryGetExample(Dictionary<string, string> map)
        {
            _map = map;
            _found = new List<string>(_map.Count);
            _missing = new List<string>(_map.Count);
        }

        public void TestTryGet()
        {
            for (int i = 0; i < _map.Count; i++)
            {
                string key = "key" + i;
                string value;
                if (_map.TryGetValue(key, out value))
                    _found.Add(key + ": " + value);
                else
                    _missing.Add(key);
            }

            Console.WriteLine(_found.Count() + " found");
            Console.WriteLine(_missing.Count() + " missing");
        }

        public void TestCallback()
        {
            for (int i = 0; i < _map.Count; i++)
                _map.Lookup("key" + i, (k, v) => _found.Add(k + ": " + v), k => _missing.Add(k));

            Console.WriteLine(_found.Count() + " found");
            Console.WriteLine(_missing.Count() + " missing");
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            int size = 2000000;
            var map = new Dictionary<string, string>(size);
            for (int i = 0; i < size; i++)
                if (i % 5 != 0)
                    map.Add("key" + i, "value" + i);

            long totalCallback = 0;
            long totalTryGet = 0;

            int iterations = 20;
            TryGetExample tryGet;
            for (int i = 0; i < iterations; i++)
            {
                tryGet = new TryGetExample(map);
                long tryGetStart = DateTime.UtcNow.Ticks;
                tryGet.TestTryGet();
                totalTryGet += (DateTime.UtcNow.Ticks - tryGetStart);
                GC.Collect();

                tryGet = new TryGetExample(map);
                long callbackStart = DateTime.UtcNow.Ticks;
                tryGet.TestCallback();
                totalCallback += (DateTime.UtcNow.Ticks - callbackStart);
                GC.Collect();
            }

            Console.WriteLine("Avg. callback: " + (totalCallback / iterations));
            Console.WriteLine("Avg. tryGet(): " + (totalTryGet / iterations));
        }
    }
}

, , , , , . , , , . , , - .

, , 10x 2000000, . 3% , . 17773437, tryget - 17234375.

, , , TryGet , , . , - , . , , if/then/else, TryGet, , , , .

, # , #. , , . .NET, , -, , , Java.

+1

, , . , , , .


, , :

  •  
  •  
  •  
  • ,

, , , API :

  •  
  • has(key), , ( ). 
  • get(key), , ; NoSuchElementException. 
  • get(key,defaultval), , . 
  • setdefault(key,defaultval), (key, defaultval), , , ( , ).

- get (key, null). API , ( , ).

, Java,() containsKey(), setdefault() putIfAbsent(). get() NoSuchElementException, .... get() null, , null, , ( API, null, , IllegalArgumentException , , null), API , setdefault() , , if (! Dict.has(key)) {dict.set(key, val); }. , , - dict.get(key).doSomething(), , get() ( )... NoSuchElementException, , Java, , NullPointerException.


, , tryGet.... , , ; tryGet out . :

OutParameter out = new OutParameter();

for , tryGet. , for out .

+2

Source: https://habr.com/ru/post/1711713/


All Articles