Почему EnumSet проседает по скорости?
От: Denys V. Украина http://ua.linkedin.com/in/dvalchuk
Дата: 24.10.11 20:22
Оценка:
NetBeans подсказала мне что нужно использовать EnumSet<Enum> вместо HashSet<Enum>...
полез я в исходник EnumSet — судя по описанию и по идее что там описано — да лучше использовать EnumSet.
накрапал я маленький тест ...

public class EnumSetTest extends TestCase {
    
    static final int IT_NUM = 100000000;
    
    public enum LType {

        SYV("syv"),
        DEC("dec"),
        BZZZZZ("bzzzzz"),
        ASDF("asdf"),
        QWERTY("qwerty"),
        A1S2("a1s2"),
        ZXCV("zxcv"),
        SPU("spu");
        String value;

        LType(String value) {
            this.value = value;
        }

        public String getValue() {
            return value;
        }

        public static LType fromValue(String v) {
            for (LType l : LType.values()) {
                if (l.value.equals(v.toLowerCase())) {
                    return l;
                }
            }
            throw new IllegalArgumentException(v);
        }

        @Override
        public String toString() {
            return value;
        }
    }
    
    public void testHashSet() {
        LType[] values = LType.values();
        HashSet<LType> types = new HashSet<LType>(values.length);
        {
            Timer t = new Timer("testHashSet:create");
            for(int i = 0; i < IT_NUM; ++i) {
                types.add(values[i % values.length]);
            }
            t.endAndEcho();
        }
        {
            Random rand = new Random();
            Timer t = new Timer("testHashSet:access");
            long f = 0;
            for (int i = 0; i < IT_NUM * IT_NUM; ++i) {
                int v = Math.abs(rand.nextInt()) % values.length;
                boolean rv = types.contains(values[v]);
                if (rv)
                    ++f;
            }
            System.out.println("found: " + Long.toString(f));
            t.endAndEcho();
        }
    }
    
    public void testEnumSet() {
        LType[] values = LType.values();
        Set<LType> types = EnumSet.noneOf(LType.class);
        {
            Timer t = new Timer("testEnumSet:create");
            for (int i = 0; i < IT_NUM; ++i) {
                types.add(values[i % values.length]);
            }
            t.endAndEcho();
        }
        {
            Random rand = new Random();
            Timer t = new Timer("testEnumSet:access");
            long f = 0;
            for (int i = 0; i < IT_NUM * IT_NUM; ++i) {
                int v = Math.abs(rand.nextInt()) % values.length;
                boolean rv = types.contains(values[v]);
                if (rv) {
                    ++f;
                }
            }
            System.out.println("found: " + Long.toString(f));
            t.endAndEcho();
        }
    }
}


Результат

testHashSet:create: 2s 453ss
found: 1874919424
testHashSet:access: 59s 453ss
testEnumSet:create: 1s 235ss
found: 1874919424
testEnumSet:access: 1m 8s 260ss


Объясните пожалуйста — почему так???
Может я банально ошибся в чем то?
С уважением Denys Valchuk

IMHO чем больше мнений тем оптимальней выбор варианта... :)
Re: Почему EnumSet проседает по скорости?
От: Skynin Украина skynin.blogspot.com
Дата: 24.10.11 21:04
Оценка: 4 (1) -1
Потому что у HashSet переопределен метод contains:

private transient HashMap<E,Object> map;

    public boolean contains(Object o) {
    return map.containsKey(o);
    }


В HashMap ищется вот так:

final Entry<K,V> getEntry(Object key) {
        int hash = (key == null) ? 0 : hash(key.hashCode());
        for (Entry<K,V> e = table[indexFor(hash, table.length)];
             e != null;
             e = e.next) {
            Object k;
            if (e.hash == hash &&
                ((k = e.key) == key || (key != null && key.equals(k))))
                return e;
        }
        return null;
    }


а у EnumSet оставлен из — java.util.AbstractCollection<E>
public boolean contains(Object o) {
    Iterator<E> e = iterator();
    if (o==null) {
        while (e.hasNext())
        if (e.next()==null)
            return true;
    } else {
        while (e.hasNext())
        if (o.equals(e.next()))
            return true;
    }
    return false;
    }
Re: Почему EnumSet проседает по скорости?
От: avpavlov  
Дата: 24.10.11 21:14
Оценка:
DV>

Ты на почти 2х миллиардах итераций получил разницу в 9 сек и назвал это "проседает по скорости"? Я бы на твоём месте не парился (а ещё лучше бы поразмышлял, почему вместо 10^16 итераций у тебя всего 2^9, хоть чему-то полезному научишься на этом примере
Re[2]: Почему EnumSet проседает по скорости?
От: Denys V. Украина http://ua.linkedin.com/in/dvalchuk
Дата: 24.10.11 21:48
Оценка:
Здравствуйте, avpavlov, Вы писали:


DV>>


A>Ты на почти 2х миллиардах итераций получил разницу в 9 сек и назвал это "проседает по скорости"? Я бы на твоём месте не парился (а ещё лучше бы поразмышлял, почему вместо 10^16 итераций у тебя всего 2^9, хоть чему-то полезному научишься на этом примере


вы намекаете на то что мое число не влезло в положительную часть signed int? Это банальная опечатка. Я просто добавил ноликов не считая.
Но про 512 итераций вы тоже загнули...
С уважением Denys Valchuk

IMHO чем больше мнений тем оптимальней выбор варианта... :)
Re[2]: Почему EnumSet проседает по скорости?
От: Nicht Россия  
Дата: 25.10.11 06:08
Оценка:
Здравствуйте, Skynin, Вы писали:

S>Потому что у HashSet переопределен метод contains:


S>а у EnumSet оставлен из — java.util.AbstractCollection<E>



EnumSet сам по себе является абстрактным классом. У конкретных реализаций RegularEnumSet и JumboEnumSet метод контайнс реализован как надо.
Re: Почему EnumSet проседает по скорости?
От: Nicht Россия  
Дата: 25.10.11 06:52
Оценка: 14 (4)
Здравствуйте, Denys V., Вы писали:

DV>NetBeans подсказала мне что нужно использовать EnumSet<Enum> вместо HashSet<Enum>...

DV>полез я в исходник EnumSet — судя по описанию и по идее что там описано — да лучше использовать EnumSet.
DV>накрапал я маленький тест ...

Как обычно и бывает, маленький тест ничего не показывает когда дело доходит то микробенчмарков.
Я переписал твой тест с использованием Google caliper.
import java.util.EnumSet;
import java.util.HashSet;
import java.util.Set;

import org.junit.Test;

import com.google.caliper.Benchmark;
import com.google.caliper.Param;
import com.google.caliper.Runner;
import com.google.caliper.SimpleBenchmark;
import com.google.common.collect.ObjectArrays;

public class SetsBenchmark extends SimpleBenchmark {

    static final int IT_NUM = 10000000;

    public static enum LType {

        SYV("syv"), DEC("dec"), BZZZZZ("bzzzzz"), ASDF("asdf"), QWERTY("qwerty"), A1S2("a1s2"), ZXCV("zxcv"), SPU(
                        "spu");
        String value;

        LType(String value) {
            this.value = value;
        }

        public String getValue() {
            return value;
        }

        public static LType fromValue(String v) {
            for (LType l : LType.values()) {
                if (l.value.equals(v.toLowerCase())) {
                    return l;
                }
            }
            throw new IllegalArgumentException(v);
        }

        @Override
        public String toString() {
            return value;
        }
    }

    @Param({"enumset", "hashset"})
    String setType;

    public void timeSet(int reps) {
        for (int r = 0; r < reps; r++) {
            LType[] values = LType.values();
            Set<LType> types = createSet(setType, values.length);
            for (int i = 0; i < IT_NUM; ++i) {
                types.add(values[i % values.length]);
            }
            long f = 0;
            for (int i = 0; i < IT_NUM; ++i) {
                int v = i % values.length;
                boolean rv = types.contains(values[v]);
                if (rv) {
                    ++f;
                }
            }
        }
    }

    private Set<LType> createSet(String type, int length) {
        if (type.equals("enumset")) {
            return EnumSet.noneOf(LType.class);
        } else {
            return new HashSet<>(length);
        }
    }

    @Test
    public void benchmarkPut() throws Exception {
        runBenchmark(SetsBenchmark.class);
    }

    private static void runBenchmark(Class<? extends Benchmark> benchmark) {
        String[] args = ObjectArrays.concat(new String[] {"--trials", "1"}, benchmark.getName());
        new Runner().run(args);
    }
}

Нулей только поменьше поставил, а то ждать долго. И рандом убрал, чтобы не путал карты.
Запустил.
Получил следующее

0% Scenario{vm=java, trial=0, benchmark=Set, setType=enumset} 216869070.00 ns; σ=353813.24 ns @ 3 trials
50% Scenario{vm=java, trial=0, benchmark=Set, setType=hashset} 439034880.33 ns; σ=766141.39 ns @ 3 trials

setType ms linear runtime
enumset 217 ==============
hashset 439 ==============================

И того получаем что EnumSet в среднем в два раза быстрее чем HashSet.
А с учетом того что RegularEnumSet использует всего одно поле long для хранения информации, то значит что оно еще и по памяти как минимум в несколько раз выгоднее.
Re[2]: Почему EnumSet проседает по скорости?
От: Denys V. Украина http://ua.linkedin.com/in/dvalchuk
Дата: 25.10.11 08:39
Оценка:
Здравствуйте, Nicht, Вы писали:

N> Google caliper.


Спасибо, за хорошую ссылку?
вы какую ревизию собирали?
у меня последняя (r354) не собирается...
С уважением Denys Valchuk

IMHO чем больше мнений тем оптимальней выбор варианта... :)
Re[3]: Почему EnumSet проседает по скорости?
От: Nicht Россия  
Дата: 25.10.11 09:24
Оценка: 2 (1)
Здравствуйте, Denys V., Вы писали:

DV>Здравствуйте, Nicht, Вы писали:


N>> Google caliper.


DV>Спасибо, за хорошую ссылку?

DV>вы какую ревизию собирали?
DV>у меня последняя (r354) не собирается...

Я мейвеном пользуюсь. Беру снепшоты из OSS репозитория Sonatype. У них там очень много зеркал всяких репозиториев в одном месте.
Re: Почему EnumSet проседает по скорости?
От: gegMOPO4  
Дата: 25.10.11 11:53
Оценка:
Здравствуйте, Denys V., Вы писали:
DV>
DV>            Random rand = new Random();
DV>            Timer t = new Timer("testHashSet:access");
DV>            long f = 0;
DV>            for (int i = 0; i < IT_NUM * IT_NUM; ++i) {
DV>                int v = Math.abs(rand.nextInt()) % values.length;
DV>                boolean rv = types.contains(values[v]);
DV>                if (rv)
DV>                    ++f;
DV>            }
DV>


Повезло, что ни в одной из 1874919424 итераций не попалось отрицательное.
Re[3]: Почему EnumSet проседает по скорости?
От: avpavlov  
Дата: 25.10.11 18:25
Оценка:
DV>Но про 512 итераций вы тоже загнули...

1:1
Re[2]: Почему EnumSet проседает по скорости?
От: xBlackCat Россия  
Дата: 26.10.11 12:33
Оценка:
Здравствуйте, gegMOPO4.
Вы писали:

MOPO> Здравствуйте, Denys V., Вы писали:

MOPO> DV>
MOPO> DV>            Random rand = new Random();
MOPO> DV>            Timer t = new Timer("testHashSet:access");
MOPO> DV>            long f = 0;
MOPO> DV>            for (int i = 0; i < IT_NUM * IT_NUM; ++i) {
MOPO> DV>                int v = Math.abs(rand.nextInt()) % values.length;
MOPO> DV>                boolean rv = types.contains(values[v]);
MOPO> DV>                if (rv)
MOPO> DV>                    ++f;
MOPO> DV>            }
MOPO> DV>

MOPO> Повезло, что ни в одной из 1874919424 итераций не попалось отрицательное.
А почему повезло? См. выделенное
Rojac v0.1 / rev. 733
Rojac &mdash; Rsdn Offline JAva Client
Анонсы и обсуждение здесь
Автор: xBlackCat
Дата: 08.02.10
Re[3]: Почему EnumSet проседает по скорости?
От: gegMOPO4  
Дата: 26.10.11 12:50
Оценка: +1
Здравствуйте, xBlackCat, Вы писали:
BC>Здравствуйте, gegMOPO4.
BC>Вы писали:
MOPO>> Здравствуйте, Denys V., Вы писали:
MOPO>> DV>
MOPO>> DV>            Random rand = new Random();
MOPO>> DV>            Timer t = new Timer("testHashSet:access");
MOPO>> DV>            long f = 0;
MOPO>> DV>            for (int i = 0; i < IT_NUM * IT_NUM; ++i) {
MOPO>> DV>                int v = Math.abs(rand.nextInt()) % values.length;
MOPO>> DV>                boolean rv = types.contains(values[v]);
MOPO>> DV>                if (rv)
MOPO>> DV>                    ++f;
MOPO>> DV>            }
MOPO>> DV>

MOPO>> Повезло, что ни в одной из 1874919424 итераций не попалось отрицательное.
BC>А почему повезло? См. выделенное

Потому, что нарваться на -2147483648 за 1874919424 попытки не так уж мала — 1-(1-1./(1<<32))**1874919424 = 35%. -Integer.MIN_VALUE == Integer.MIN_VALUE.

И вообще, читайте документацию на то, что используете. В частности, Random.nextInt(int).
Re[4]: Почему EnumSet проседает по скорости?
От: xBlackCat Россия  
Дата: 26.10.11 12:58
Оценка:
Здравствуйте, gegMOPO4.
Вы писали:

MOPO> Здравствуйте, xBlackCat, Вы писали:

MOPO> BC>А почему повезло? См. выделенное
MOPO> Потому, что нарваться на -2147483648 за 1874919424 попытки не так уж мала — 1-(1-1./(1<<32))**1874919424 = 35%. -Integer.MIN_VALUE == Integer.MIN_VALUE.
MOPO> И вообще, читайте документацию на то, что используете. В частности, Random.nextInt(int).

Да. Действительно. Как-то ускользнул этот момент. Лучше было бы использовать
rand.nextInt(values.length);
Rojac v0.1 / rev. 733
Rojac &mdash; Rsdn Offline JAva Client
Анонсы и обсуждение здесь
Автор: xBlackCat
Дата: 08.02.10
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.