{"id":502,"date":"2013-11-23T23:42:04","date_gmt":"2013-11-23T22:42:04","guid":{"rendered":"https:\/\/arliguy.net\/?p=502"},"modified":"2014-01-28T22:50:35","modified_gmt":"2014-01-28T21:50:35","slug":"micro-benchmark-de-convertisseurs-de-tableaux-de-bytes-vers-representation-hexa-avec-java","status":"publish","type":"post","link":"https:\/\/arliguy.net\/2013\/11\/23\/micro-benchmark-de-convertisseurs-de-tableaux-de-bytes-vers-representation-hexa-avec-java\/","title":{"rendered":"Micro benchmark de convertisseurs de tableaux de bytes vers repr\u00e9sentation Hexa avec Java"},"content":{"rendered":"
Il y a quelques temps maintenant je suis tomb\u00e9 sur une offre d’emploi\u00a0de la soci\u00e9t\u00e9 ARCA Computing<\/a>. Cette\u00a0annonce est int\u00e9ressante<\/a> dans sa pr\u00e9sentation, car pour pouvoir candidater il fallait r\u00e9soudre une petite \u00e9nigme qui consistait \u00e0 r\u00e9cup\u00e9rer un projet h\u00e9berg\u00e9 sur github<\/a>, l’ex\u00e9cuter en remplissant quelques trous pour obtenir le contenu de l’annonce depuis le site web d’ARCA.<\/p>\n Bon, j’avoue, j’ai trich\u00e9 car j’ai juste r\u00e9cup\u00e9r\u00e9 le bout de code int\u00e9ressant pour obtenir l’url, mis cela dans mon IDE pour obtenir le lien que j’ai ouvert directement dans un navigateur. Mais bref, l\u00e0 n’est pas le sujet\u2026<\/p>\n Ce qui a piqu\u00e9 ma curiosit\u00e9, c’est la m\u00e9thode convertToHex<\/em> du petit exercice<\/a>. J’avais souvenir d’avoir d\u00e9j\u00e0 utilis\u00e9 ce genre de m\u00e9thode, mais pas avec cette impl\u00e9mentation. Je me suis donc demand\u00e9 si elle \u00e9tait plus int\u00e9ressante que d’autres qu’on peut trouver ici et l\u00e0. Apr\u00e8s une petite recherche, je suis tomb\u00e9 sur un message de Stack Overflow qui proposait plusieurs impl\u00e9mentations : http:\/\/stackoverflow.com\/questions\/9655181\/convert-from-byte-array-to-hex-string-in-java<\/a>. Cela permettait d’avoir une bonne liste pour faire un petit comparatif \u00e0 l\u2019arrache.<\/p>\n Le principe est simple :<\/p>\n Ci-dessous les diff\u00e9rentes impl\u00e9mentations que j’ai collect\u00e9es. J’ai tout plac\u00e9 dans une seule classe pour te faciliter la vie lecteur : si tu veux reproduire, pas besoin de t\u00e9l\u00e9charger une archive et tout le bataclan. Cr\u00e9er une nouvelle classe et copier\/coller le code qui suit te permettra de tester rapidement et simplement de ton c\u00f4t\u00e9. Le “Converter 0<\/em>” est l’impl\u00e9mentation extraite du code de l’annonce. Les “Converter 1<\/em>“, “Converter 2<\/em>“, “Converter 3<\/em>“, “Converter 5<\/em>“, “Converter 6<\/em>“, “Converter 7<\/em>” (il s’agit d’une l\u00e9g\u00e8re variante du 6 qui n’apporte rien, j’aurai d\u00fb le supprimer),\u00a0 et “Converter 8<\/em>” sont extraits de l’article de Stack Overflow ou des articles point\u00e9s. Le “Converter 4<\/em>” est un m\u00e9lange que j’ai r\u00e9alis\u00e9 entre les “Converter 3<\/em>” et “Converter 5<\/em>”<\/p>\n Voici ce que j’obtiens sur ma machine :<\/p>\n NB : Attention, cela ne repr\u00e9sente que le r\u00e9sultat d’une ex\u00e9cution sur ma machine, il n’est pas garanti qu’ailleurs le r\u00e9sultat soit identique.<\/p>\n Il ressort donc que le “Converter 0<\/em>” est pile-poile au milieu de la liste. Que le “Converter 4<\/em>” que j’ai fait en mixant le 3 et le 5 se comporte bien mieux qu’eux et qu’il \u00e9gale m\u00eame le plus rapide qui est le “Converter 6<\/em>” (le 7 \u00e9tant une variante inutile).<\/p>\n Que conclure de ce petit test ? Que si vous avez besoin de faire plus de 40 000 conversions de tableaux de bytes en repr\u00e9sentation hexa en moins de 300 ms sur de gros tableaux de bytes, alors il faut faire attention \u00e0 l’algo utilis\u00e9. Sinon \u2026 Il y a peut \u00eatre une meilleure fa\u00e7on de passer le temps :)<\/p>\n Et je me dis maintenant qu’il faudrait vraiment que j’essaye un framework de micro-benchmarks r\u00e9alis\u00e9 par des gens plus comp\u00e9tents que moi pour voir ce que cela donne. J’ai not\u00e9 comme outils :<\/p>\n <\/p>\n","protected":false},"excerpt":{"rendered":" Contexte Il y a quelques temps maintenant je suis tomb\u00e9 sur une offre d’emploi\u00a0de la soci\u00e9t\u00e9 ARCA Computing. Cette\u00a0annonce est int\u00e9ressante dans sa pr\u00e9sentation, car pour pouvoir candidater il fallait r\u00e9soudre une petite \u00e9nigme qui consistait \u00e0 r\u00e9cup\u00e9rer un projet h\u00e9berg\u00e9 sur github, l’ex\u00e9cuter en remplissant quelques trous pour obtenir le contenu de l’annonce depuis … Continuer la lecture de Micro benchmark de convertisseurs de tableaux de bytes vers repr\u00e9sentation Hexa avec Java<\/span> \n
Le code<\/h1>\n
package net.ozim;\r\n\r\nimport java.math.BigInteger;\r\nimport java.nio.charset.StandardCharsets;\r\nimport java.security.NoSuchAlgorithmException;\r\nimport java.util.ArrayList;\r\nimport java.util.List;\r\n\r\n\/**\r\n *\r\n * @author <a href=\"mailto:dev@arliguy.net\">Bruno ARLIGUY<\/a>\r\n *\/\r\npublic class HexaString {\r\n\r\n private interface Converter\r\n {\r\n public String execute(final byte[] data);\r\n public String getName();\r\n\r\n public boolean isExcluded();\r\n public boolean excluded();\r\n }\r\n\r\n private static abstract class AbstractConverter implements Converter {\r\n private int _count = 0;\r\n\r\n @Override\r\n public boolean isExcluded() {\r\n return _count > 2;\r\n }\r\n\r\n @Override\r\n public boolean excluded() {\r\n _count ++;\r\n\r\n return this.isExcluded();\r\n }\r\n }\r\n\r\n private static class Converter0 extends AbstractConverter {\r\n @Override\r\n public final String execute(final byte[] data) {\r\n final StringBuilder sb = new StringBuilder(2 * data.length);\r\n for (int i = 0; i < data.length; i++) {\r\n int halfbyte = (data[i] >>> 4) & 0x0F;\r\n int two_halfs = 0;\r\n do {\r\n if ((0 <= halfbyte) && (halfbyte <= 9)) {\r\n sb.append((char) ('0' + halfbyte));\r\n }\r\n else {\r\n sb.append((char) ('a' + (halfbyte - 10)));\r\n }\r\n halfbyte = data[i] & 0x0F;\r\n } while (two_halfs++ < 1);\r\n }\r\n return sb.toString();\r\n }\r\n\r\n @Override\r\n public final String getName() {\r\n return \"Converter 0\";\r\n }\r\n }\r\n\r\n private static class Converter1 extends AbstractConverter {\r\n @Override\r\n public final String execute(final byte[] data) {\r\n final StringBuilder sb = new StringBuilder(2 * data.length);\r\n for(byte b: data) {\r\n sb.append(String.format(\"%02x\", b&0xff));\r\n }\r\n return sb.toString();\r\n }\r\n\r\n @Override\r\n public final String getName() {\r\n return \"Converter 1\";\r\n }\r\n }\r\n\r\n private static class Converter2 extends AbstractConverter {\r\n @Override\r\n public final String execute(final byte[] data) {\r\n final StringBuilder sb = new StringBuilder(2 * data.length);\r\n for(byte b: data) {\r\n sb.append(Integer.toString( ( b & 0xff ) + 0x100, 16).substring( 1 ));\r\n }\r\n\r\n return sb.toString();\r\n }\r\n\r\n @Override\r\n public final String getName() {\r\n return \"Converter 2\";\r\n }\r\n }\r\n\r\n private static class Converter3 extends AbstractConverter {\r\n private static final String _chars = \"0123456789abcdef\";\r\n\r\n @Override\r\n public final String execute(final byte[] data) {\r\n final StringBuilder sb = new StringBuilder(2 * data.length);\r\n for (final byte b : data) {\r\n sb.append(_chars.charAt((b & 0xF0) >>> 4)).append(_chars.charAt((b & 0x0F)));\r\n }\r\n return sb.toString();\r\n }\r\n\r\n @Override\r\n public final String getName() {\r\n return \"Converter 3\";\r\n }\r\n }\r\n\r\n \/**\r\n * Un m\u00e9lange du converter3 et converter5\r\n *\/\r\n private static class Converter4 extends AbstractConverter {\r\n private static final String _chars = \"0123456789abcdef\";\r\n\r\n @Override\r\n public final String execute(final byte[] data) {\r\n final char[] result = new char[2 * data.length];\r\n int index = 0;\r\n\r\n for (final byte b : data) {\r\n result[index++] = _chars.charAt((b & 0xF0) >>> 4);\r\n result[index++] = _chars.charAt((b & 0x0F));\r\n }\r\n return new String(result);\r\n }\r\n\r\n @Override\r\n public final String getName() {\r\n return \"Converter 4\";\r\n }\r\n }\r\n\r\n private static class Converter5 extends AbstractConverter {\r\n private static final byte[] _chars = {\r\n (byte)'0', (byte)'1', (byte)'2', (byte)'3',\r\n (byte)'4', (byte)'5', (byte)'6', (byte)'7',\r\n (byte)'8', (byte)'9', (byte)'a', (byte)'b',\r\n (byte)'c', (byte)'d', (byte)'e', (byte)'f'\r\n };\r\n\r\n @Override\r\n public final String execute(final byte[] data) {\r\n final byte[] hex = new byte[2 * data.length];\r\n int index = 0;\r\n\r\n for (byte b : data) {\r\n final int v = b & 0xFF;\r\n hex[index++] = _chars[v >>> 4];\r\n hex[index++] = _chars[v & 0xF];\r\n }\r\n return new String(hex, StandardCharsets.US_ASCII);\r\n }\r\n\r\n @Override\r\n public final String getName() {\r\n return \"Converter 5\";\r\n }\r\n }\r\n\r\n private static class Converter6 extends AbstractConverter {\r\n private static final char[] _chars = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};\r\n\r\n @Override\r\n public final String execute(final byte[] data) {\r\n final char[] hexChars = new char[data.length * 2];\r\n int v;\r\n for (int j = 0; j < data.length; j++) {\r\n v = data[j] & 0xFF;\r\n hexChars[j * 2] = _chars[v >>> 4];\r\n hexChars[j * 2 + 1] = _chars[v & 0x0F];\r\n }\r\n return new String(hexChars);\r\n }\r\n\r\n @Override\r\n public final String getName() {\r\n return \"Converter 6\";\r\n }\r\n }\r\n\r\n private static class Converter7 extends AbstractConverter {\r\n private static final String _chars = \"0123456789abcdef\";\r\n\r\n @Override\r\n public final String execute(final byte[] data) {\r\n final char[] hexChars = new char[data.length * 2];\r\n int v;\r\n for (int j = 0; j < data.length; j++) {\r\n v = data[j] & 0xFF;\r\n hexChars[j * 2] = _chars.charAt(v >>> 4);\r\n hexChars[j * 2 + 1] = _chars.charAt(v & 0x0F);\r\n }\r\n return new String(hexChars);\r\n }\r\n\r\n @Override\r\n public final String getName() {\r\n return \"Converter 7\";\r\n }\r\n }\r\n\r\n \/**\r\n * Note : Using this will remove the leading zeros\r\n *\/\r\n private static class Converter8 extends AbstractConverter {\r\n @Override\r\n public final String execute(final byte[] data) {\r\n return new BigInteger(1, data).toString(16);\r\n }\r\n\r\n @Override\r\n public final String getName() {\r\n return \"Converter 8\";\r\n }\r\n }\r\n\r\n private static class ExclusionStep {\r\n private final Converter _converter;\r\n private final int _step;\r\n\r\n public ExclusionStep(final Converter converter, final int step) {\r\n _converter = converter;\r\n _step = step;\r\n }\r\n\r\n @Override\r\n public String toString() {\r\n return String.format(\"%s at step %4d\", _converter.getName(), _step);\r\n }\r\n }\r\n\r\n private static long doTest(final Converter c, final byte[] data, final int loops) {\r\n final long start = System.currentTimeMillis();\r\n\r\n for (int i = loops; i > 0; i--) {\r\n c.execute(data);\r\n }\r\n\r\n final long stop = System.currentTimeMillis();\r\n\r\n return (stop - start);\r\n }\r\n\r\n private final static int LOOPS = 40_000;\r\n private final static int EXCLUSION_DELAY = 300;\r\n\r\n public static void main(final String[] args) throws NoSuchAlgorithmException {\r\n final String base = \"\u20ac\u20ac ma base \u00e0 mettre en hex string\u2026 \";\r\n final List<Converter> converters = new ArrayList<>();\r\n\r\n converters.add(new Converter0());\r\n converters.add(new Converter1());\r\n converters.add(new Converter2());\r\n converters.add(new Converter3());\r\n converters.add(new Converter4());\r\n converters.add(new Converter5());\r\n converters.add(new Converter6());\r\n converters.add(new Converter7());\r\n converters.add(new Converter8());\r\n\r\n final byte[] validation = base.getBytes(StandardCharsets.UTF_8);\r\n\r\n for (Converter c : converters) {\r\n System.out.println(String.format(\"validation %s : %s\", c.getName(), c.execute(validation)));\r\n }\r\n\r\n \/\/warmup\r\n for (Converter c : converters) {\r\n doTest(c, validation, LOOPS);\r\n }\r\n\r\n boolean stop;\r\n int count = 0;\r\n final List<ExclusionStep> exclusions = new ArrayList<>();\r\n do {\r\n \/\/create content to convert, longer at each loop\r\n final String current = new String(new char[++count]).replace(\"\\0\", base);\r\n final byte[] data = current.getBytes(StandardCharsets.UTF_8);\r\n\r\n System.out.println(String.format(\"step %d\", count));\r\n\r\n int countExcluded = 0;\r\n for (Converter c : converters) {\r\n if (c.isExcluded()) {\r\n countExcluded ++;\r\n }\r\n else {\r\n final long duration = doTest(c, data, LOOPS);\r\n if (duration > EXCLUSION_DELAY && c.excluded()) {\r\n exclusions.add(new ExclusionStep(c, count));\r\n System.out.println(String.format(\"Excluding %s\", c.getName()));\r\n }\r\n }\r\n }\r\n\r\n stop = countExcluded == converters.size();\r\n\r\n } while (! stop);\r\n\r\n for (ExclusionStep exclusion : exclusions) {\r\n System.out.println(exclusion.toString());\r\n }\r\n }\r\n}<\/pre>\n
Le r\u00e9sultat<\/h1>\n
Converter 1 at step\u00a0\u00a0\u00a0 3\r\nConverter 8 at step\u00a0\u00a0\u00a0 5\r\nConverter 2 at step\u00a0\u00a0\u00a0 6\r\nConverter 3 at step\u00a0\u00a0 39\r\nConverter 0 at step\u00a0\u00a0 42\r\nConverter 5 at step\u00a0\u00a0 78\r\nConverter 4 at step\u00a0 111\r\nConverter 7 at step\u00a0 112\r\nConverter 6 at step\u00a0 113<\/pre>\n
\n