読者です 読者をやめる 読者になる 読者になる

HHeLiBeXの日記 正道編

日々の記憶の記録とメモ‥

Map系クラスのメモリ使用量の違い

Java

唐突に、HashMap、LinkedHashMap、TreeMapのメモリ使用量の違いを測ってみようと思った。
ただ、メモリ使用量、というよりは、いくつのkey-valueを保持できるか、というところに力点を置いた結果の示し方をする。
検証用コードはこんな感じ。

import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.TreeMap;

public class Main {

    private static NumberFormat ID_GEN_30 = new DecimalFormat(
            "000000000000000000000000000000");
    private static NumberFormat ID_GEN_60 = new DecimalFormat(
            "000000000000000000000000000000000000000000000000000000000000");
    private static NumberFormat ID_GEN_120 = new DecimalFormat(
            "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000");

    /**
     * @param args
     */
    public static void main(String[] args) {
        Map map;
        if (System.getProperty("map").equals("linkedHash")) {
            map = new LinkedHashMap();
        } else if (System.getProperty("map").equals("tree")) {
            map = new TreeMap();
        } else {
            map = new HashMap();
        }

        long id = 0;
        try {
            for (; true; ++id) {
                if (id % 10000 == 0) {
                    System.out.println(id);
                    dumpMemoryUsage();
                }
                Object idObj = generate(id);
                map.put(idObj, idObj);
            }
        } finally {
            map.clear();
            System.out.print(id);
            System.err.print(id);
        }
    }

    private static Object generate(long id) {
        if (System.getProperty("generator").equals("string30")) {
            return ID_GEN_30.format(id);
        } else if (System.getProperty("generator").equals("string60")) {
            return ID_GEN_60.format(id);
        } else if (System.getProperty("generator").equals("string120")) {
            return ID_GEN_120.format(id);
        } else {
            return new Long(id);
        }
    }
    private static void dumpMemoryUsage() {
        Runtime rt = Runtime.getRuntime();
        long max = rt.maxMemory();
        long total = rt.totalMemory();
        long free = rt.freeMemory();
        long used = (total - free);
        System.out.println("  maxMemory = " + (max / 1024.0 / 1024));
        System.out.println("totalMemory = " + (total / 1024.0 / 1024));
        System.out.println(" freeMemory = " + (free / 1024.0 / 1024));
        System.out.println(" usedMemory = " + (used / 1024.0 / 1024));
    }
}

実行時のプロパティ値によって、検証内容を切り替えられるようにしている。
また、一応、メモリ使用状況も出力するようにしているが、今回は省略。確認されたい方はご自身で。
で、これを次のようなバッチを作って実行する。

@ECHO OFF

SETLOCAL

PUSHD %~dp0

CALL :TEST 1 j2sdk1.4.2_18
CALL :TEST 2 j2sdk1.4.2_18
CALL :TEST 3 j2sdk1.4.2_18
CALL :TEST 1 jdk1.5.0_11
CALL :TEST 2 jdk1.5.0_11
CALL :TEST 3 jdk1.5.0_11
CALL :TEST 1 jdk1.6.0_01
CALL :TEST 2 jdk1.6.0_01
CALL :TEST 3 jdk1.6.0_01

GOTO END

:TEST
SETLOCAL
SET JAVA_HOME=C:\%2
SET PATH=%JAVA_HOME%\bin;%PATH%

java -version

SET LOGDIR=result-%1-%2
MKDIR %LOGDIR%
CALL :RUN hash       string30  %LOGDIR%
CALL :RUN linkedHash string30  %LOGDIR%
CALL :RUN tree       string30  %LOGDIR%
CALL :RUN hash       string60  %LOGDIR%
CALL :RUN linkedHash string60  %LOGDIR%
CALL :RUN tree       string60  %LOGDIR%
CALL :RUN hash       string120 %LOGDIR%
CALL :RUN linkedHash string120 %LOGDIR%
CALL :RUN tree       string120 %LOGDIR%
CALL :RUN hash       long      %LOGDIR%
CALL :RUN linkedHash long      %LOGDIR%
CALL :RUN tree       long      %LOGDIR%
ENDLOCAL
EXIT /B

:RUN
java -Dmap=%1 -Dgenerator=%2 -classpath classes Main > %3\result-%2-%1.txt
EXIT /B

:END
ENDLOCAL

これによって得られた各結果を集計するためのスクリプト

#! /bin/bash

for v in j2sdk1.4.2_18 jdk1.5.0_11 jdk1.6.0_01 ; do
    for f in `ls result-1-${v}` ; do
        echo -n "${f},${v},"
        for d in result-*-${v} ; do
            tail -1 ${d}/${f}
            echo -n ','
        done
        echo
    done
done |\
    awk -F ',' '
    BEGIN {
    }
    {
        generator_map = $1;
        version = $2;
        val1 = $3;
        val2 = $4;
        val3 = $5;

        gsub(/^result[-]/, "", generator_map);
        gsub(/[.]txt$/, "", generator_map);
        split(generator_map, ary, "-");
        generator = ary[1];
        map = ary[2];
        avg = (val1 + val2 + val3) / 3.0;
        if (_generator != generator || _version != version) {
            base = avg;
        }
        printf("%-13s %-10s %-10s %12d %12d %12d %16.3f %6.1f%%\n",
            version, generator, map, val1, val2, val3, avg, 100 * (avg / base));

        _generator = generator;
        _version = version;
    }'

そして、集計結果。

バージョン オブジェクト Map 1回目 2回目 3回目 平均 比率(hashを100%)
j2sdk1.4.2_18 long hash 1453529 1453529 1453529 1453529.000 100.0%
linkedHash 1211273 1211273 1211273 1211273.000 83.3%
tree 1386037 1386037 1386037 1386037.000 95.4%
string30 hash 486989 486989 486989 486989.000 100.0%
linkedHash 458342 458342 458342 458342.000 94.1%
tree 489183 489183 489183 489183.000 100.5%
string60 hash 322158 322158 322158 322158.000 100.0%
linkedHash 309767 309767 309767 309767.000 96.2%
tree 319850 319850 319850 319850.000 99.3%
string120 hash 190349 190349 190349 190349.000 100.0%
linkedHash 186022 186022 186022 186022.000 97.7%
tree 189002 189002 189002 189002.000 99.3%
jdk1.5.0_11 long hash 1452091 1452091 1452091 1452091.000 100.0%
linkedHash 1210075 1210075 1210075 1210075.000 83.3%
tree 1384839 1384839 1384839 1384839.000 95.4%
string30 hash 518975 518975 518975 518975.000 100.0%
linkedHash 486539 486539 486539 486539.000 93.7%
tree 519307 519307 519307 519307.000 100.1%
string60 hash 349859 349859 349859 349859.000 100.0%
linkedHash 335281 335281 335281 335281.000 95.8%
tree 346204 346204 346204 346204.000 99.0%
string120 hash 211756 211756 211756 211756.000 100.0%
linkedHash 206326 206326 206326 206326.000 97.4%
tree 213048 213048 213048 213048.000 100.6%
jdk1.6.0_01 long hash 1451522 1451523 1451522 1451522.333 100.0%
linkedHash 1209601 1209601 1209601 1209601.000 83.3%
tree 1384365 1384365 1384365 1384365.000 95.4%
string30 hash 518785 518785 518785 518785.000 100.0%
linkedHash 486361 486361 486361 486361.000 93.8%
tree 519129 519129 519129 519129.000 100.1%
string60 hash 349735 349735 349735 349735.000 100.0%
linkedHash 335163 335163 335163 335163.000 95.8%
tree 346086 346086 346086 346086.000 99.0%
string120 hash 211681 211681 211681 211681.000 100.0%
linkedHash 206253 206253 206253 206253.000 97.4%
tree 212975 212975 212975 212975.000 100.6%

まぁ、LinkedHashMapはHashMapよりメモリをいくらか余分に必要とするんだということを再確認できた、と(謎)
TreeMapで100%を超えている部分があったのはちょっと意外な感じ。