{"id":1180,"date":"2020-12-08T12:04:35","date_gmt":"2020-12-08T11:04:35","guid":{"rendered":"https:\/\/www.loicmathieu.fr\/wordpress\/?p=1180"},"modified":"2020-12-08T12:04:35","modified_gmt":"2020-12-08T11:04:35","slug":"benchmark-conversion-de-long-en-byte","status":"publish","type":"post","link":"https:\/\/www.loicmathieu.fr\/wordpress\/fr\/informatique\/benchmark-conversion-de-long-en-byte\/","title":{"rendered":"Benchmark : conversion de long en byte[]"},"content":{"rendered":"<p>J&rsquo;utilise beaucoup Kafka ces derniers temps, et dans Kafka, beaucoup de choses sont des tableaux de bytes, m\u00eame les headers !<\/p>\n<p>Comme j&rsquo;ai de nombreux composants qui s&rsquo;\u00e9changent des messages, j&rsquo;ai ajout\u00e9 des headers pour aider au suivi des messages, et entre autres un header <code>timestamp<\/code> qui a comme valeur <code>System.currentTimeMillis()<\/code>.<\/p>\n<p>Il m&rsquo;a donc fallut transformer un <code>long<\/code> en tableau de byte; d&rsquo;une mani\u00e8re tr\u00e8s na\u00efve, j&rsquo;ai cod\u00e9 \u00e7a : <code>String.valueOf(System.currentTimeMillis()).getBytes()<\/code>. Mais instancier une <code>String<\/code> \u00e0 chaque cr\u00e9ation de header ne me semble pas tr\u00e8s optimale!<\/p>\n<p>En cherchant un peu, Guava a une solution bas\u00e9e sur les op\u00e9rations binaires (bitwise calculation) via la classe <code>Longs<\/code>, ainsi que Kafka via son <code>LongSerializer<\/code>. On peut aussi utiliser un <code>ByteBuffer<\/code> pour r\u00e9aliser la conversion.<\/p>\n<p>Pour comparer les trois, rien de mieux que JMH &#8211; <a href=\"https:\/\/github.com\/openjdk\/jmh\" target=\"_blank\" rel=\"noopener noreferrer\">The Java Microbenchmark Harness<\/a>. Cet outil nous permet d\u2019\u00e9crire des micro-benchmarks pertinents en tenant compte des caract\u00e9ristiques internes de la JVM. Il offre aussi des outils int\u00e9gr\u00e9s pour analyser les performances de nos tests (profiler, disassembler, &#8230;). Si vous ne connaissez pas JMH, vous pouvez vous r\u00e9f\u00e9rer \u00e0 cet article : <a href=\"https:\/\/www.loicmathieu.fr\/wordpress\/informatique\/introduction-a-jmh-java-microbenchmark-harness\/\" target=\"_blank\" rel=\"noopener noreferrer\">INTRODUCTION \u00c0 JMH \u2013 JAVA MICROBENCHMARK HARNESS<\/a>.<\/p>\n<h2>Le benchmark<\/h2>\n<p>Tout d&rsquo;abord, j&rsquo;ai configur\u00e9 le benchmark avec un State au Thread pour que le setup soit jou\u00e9 pour chaque Thread. Entre autre, je cr\u00e9\u00e9 un <code>ByteBuffer<\/code> par thread pour comparer une impl\u00e9mentation avec et sans r\u00e9-utilisation du buffer.<\/p>\n<pre>\n@State(Scope.Thread)\n\/\/ other JMH annotations ...\npublic class LongToByteArray {\n    private static final LongSerializer LONG_SERIALIZER = new LongSerializer();\n\n    long timestamp;\n    ByteBuffer perThreadBuffer;\n\n    @Setup\n    public void setup() {\n        timestamp = System.currentTimeMillis();\n        perThreadBuffer = ByteBuffer.allocate(Long.BYTES);\n    }\n\n    \/\/ benchmark methods\n}\n<\/pre>\n<p>Ensuite, j\u2019impl\u00e9mente une m\u00e9thode de benchmark pour chaque mani\u00e8re de convertir un <code>long<\/code> en <code>byte[]<\/code>. J&rsquo;ai impl\u00e9ment\u00e9 deux algorithmes diff\u00e9rents pour <code>ByteBuffer<\/code> : l&rsquo;un avec une instanciation d&rsquo;un buffer \u00e0 chaque conversion, et l&rsquo;autre avec un recyclage d&rsquo;un buffer existant en utilisant le <code>ByteBuffer<\/code> instanci\u00e9 dans la phase de setup du benchmark.<\/p>\n<pre>    \n@Benchmark\n    public byte[] testStringValueOf() {\n        return String.valueOf(timestamp).getBytes();\n    }\n\n    @Benchmark\n    public byte[] testGuava() {\n        return Longs.toByteArray(timestamp);\n    }\n\n    @Benchmark\n    public byte[] testKafkaSerde() {\n        return LONG_SERIALIZER.serialize(null, timestamp);\n    }\n\n    @Benchmark\n    public byte[] testByteBuffer() {\n        ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES);\n        buffer.putLong(timestamp);\n        return buffer.array();\n    }\n\n    @Benchmark\n    public byte[] testByteBuffer_reuse() {\n        perThreadBuffer.putLong(timestamp);\n        byte[] result = perThreadBuffer.array();\n        perThreadBuffer.clear();\n        return result;\n    }\n<\/pre>\n<p>Le benchmark complet est accessible <a href=\"https:\/\/github.com\/loicmathieu\/jmh-benchmarks\/blob\/master\/src\/main\/java\/fr\/loicmathieu\/jmh\/LongToByteArray.java\" target=\"_blank\" rel=\"noopener noreferrer\">ici<\/a>.<\/p>\n<h2>Les r\u00e9sultats<\/h2>\n<p>Tous les tests ont \u00e9t\u00e9 ex\u00e9cut\u00e9s sur mon laptop : Intel(R) Core(TM) i7-8750H 6 coeurs (12 avec hyperthreading) &#8211; Ubuntu 19.10.<\/p>\n<p>La version de Java utilis\u00e9 est <code>openjdk version &quot;11.0.7&quot; 2020-04-14<\/code>.<\/p>\n<pre>\nBenchmark                             Mode  Cnt   Score   Error  Units\nLongToByteArray.testByteBuffer        avgt    5   4,429 \u00b1 0,204  ns\/op\nLongToByteArray.testByteBuffer_reuse  avgt    5   5,655 \u00b1 0,793  ns\/op\nLongToByteArray.testGuava             avgt    5   6,422 \u00b1 0,428  ns\/op\nLongToByteArray.testKafkaSerde        avgt    5   9,103 \u00b1 1,515  ns\/op\nLongToByteArray.testStringValueOf     avgt    5  39,660 \u00b1 4,372  ns\/op\n<\/pre>\n<p>Premi\u00e8re constatation : mon intuition \u00e9tait bonne, instancier une <code>String<\/code> pour chaque conversion est tr\u00e8s mauvais, 4 \u00e0 10 fois plus long que toutes les autres impl\u00e9mentations. Quand on regarde le r\u00e9sultat de la conversion, on comprend pourquoi. En utilisant une <code>String<\/code> on ne convertit plus un nombre sur 64bit mais une cha\u00eene de caract\u00e8res o\u00f9 chaque caract\u00e8re (chaque chiffre du nombre) est cod\u00e9 sur un byte. Donc on ne compare pas exactement la m\u00eame chose puisque le r\u00e9sultat de la conversion via une <code>String<\/code> va donner un tableau de 13 bytes, alors qu&rsquo;un <code>Long<\/code> est encod\u00e9 en 8 bytes, ce que nous donne bien la conversion via Guava, Kafka ou un ByteBuffer.<\/p>\n<p>D&rsquo;une mani\u00e8re surprenante Kafka, qui est connu pour sa performance, a une impl\u00e9mentation plus lente que Guava ou que celle via un <code>ByteBuffer<\/code>.<\/p>\n<p>Les r\u00e9sultats obtenus via <code>ByteBuffer<\/code> sont surprenants, l&rsquo;instanciation d&rsquo;un <code>ByteBuffer<\/code> pour chaque conversion est plus performante que la r\u00e9utilisation d&rsquo;un existant (qui n\u00e9cessite un clean du buffer).<\/p>\n<h2>Analyse un peu plus pouss\u00e9e<\/h2>\n<p>Laissons de c\u00f4t\u00e9 l&rsquo;impl\u00e9mentation via une <code>String<\/code> et essayons de mieux comprendre les diff\u00e9rences entre les autres impl\u00e9mentations.<\/p>\n<p>Pour cela je vais utiliser les capacit\u00e9s de profiling de JMH via l&rsquo;option <code>-prof<\/code>.<\/p>\n<p>Si on profile les allocations m\u00e9moire via <code>-prof gc<\/code> on a les r\u00e9sultats suivants :<\/p>\n<pre>\nLongToByteArray.testByteBuffer \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 avgt    5     4,492 \u00b1   0,708   ns\/op\nLongToByteArray.testByteBuffer:\u00b7gc.alloc.rate\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 avgt    5  4635,903 \u00b1 712,889  MB\/sec\nLongToByteArray.testByteBuffer_reuse \u00a0 \u00a0 \u00a0 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 avgt    5     5,798 \u00b1   1,139   ns\/op\nLongToByteArray.testByteBuffer_reuse:\u00b7gc.alloc.rate avgt    5    \u2248 10\u207b\u2074            MB\/sec\nLongToByteArray.testGuava\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 avgt    5     6,939 \u00b1   0,899   ns\/op\nLongToByteArray.testGuava:\u00b7gc.alloc.rate\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 avgt    5  3000,818 \u00b1 376,613  MB\/sec\nLongToByteArray.testKafkaSerde \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0\u00a0 avgt    5     9,317 \u00b1   0,842   ns\/op\nLongToByteArray.testKafkaSerde:\u00b7gc.alloc.rate \u00a0 \u00a0 \u00a0 avgt    5  4467,791 \u00b1 405,897  MB\/sec\n<\/pre>\n<p>On voit bien l\u2019int\u00e9r\u00eat de la r\u00e9utilisation du <code>ByteBuffer<\/code> : il n&rsquo;y a aucune allocation m\u00e9moire, alors qu&rsquo;en cr\u00e9ant un nouveau buffer pour chaque conversion, on a 4 GB\/s d&rsquo;allocation m\u00e9moire !<\/p>\n<p>Par contre, les allocations m\u00e9moires sont proches pour les trois autres impl\u00e9mentations, cela ne nous donne donc pas beaucoup plus d&rsquo;informations.<\/p>\n<p>Essayons maintenant de profiler le CPU avec <code>-prof perf<\/code> qui va utiliser l&rsquo;outil perf pour profiler l&rsquo;application.<\/p>\n<p>Les r\u00e9sultats ne sont pas facilement compr\u00e9hensibles (pour les voir c&rsquo;est <a href=\"https:\/\/github.com\/loicmathieu\/jmh-benchmarks\/blob\/master\/run\/LongToByteArray\/perf-profile.txt\" target=\"_blank\" rel=\"noopener noreferrer\">ici<\/a>), quelques constatations :<\/p>\n<ul><li>La r\u00e9-utilisation d&rsquo;un <code>ByteBuffer<\/code> semble impliquer beaucoup plus de branches CPU, c&rsquo;est peut-\u00eatre la cause de la diff\u00e9rence de performance.<\/li>\n\n<li>L&rsquo;impl\u00e9mentation Kafka semble impliquer plus de branches CPU que celle de Guava malgr\u00e9 qu&rsquo;elle effectue moins d&rsquo;instructions. \u00c0 cause de ces branches, moins d&rsquo;instructions peuvent \u00eatre r\u00e9alis\u00e9es par cycle CPU. C&rsquo;est certainement la raison qui fait que l&rsquo;impl\u00e9mentation Guava est plus efficace.<\/li>\n<\/ul>\n<p>Pour finir, par curiosit\u00e9, j&rsquo;ai \u00e9t\u00e9 regarder le code de <code>HeapByteBuffer.putLong()<\/code>, c&rsquo;est l&rsquo;impl\u00e9mentation utilis\u00e9e via <code>ByteBuffer<\/code> car je ne fais pas d&rsquo;allocation directe. Celle-ci utilise une m\u00e9thode <code>Unsafe.putLongUnaligned()<\/code>. <code>Unsafe<\/code> est connu pour ses impl\u00e9mentations hautement performantes (mais ne doit pas \u00eatre utilis\u00e9 par tout le monde), ici, de plus, cette m\u00e9thode est annot\u00e9e par <a>@HotSpotIntrinsicCandidate<\/code>code&gt;@HotSpotIntrinsicCandidate&lt;\/code<\/a> ce qui veut dire qu&rsquo;un <strong>intrinsic<\/strong> existe certainement pour elle et peut expliquer sa diff\u00e9rence de performance avec les autres impl\u00e9mentations. Un <strong>intrinsic<\/strong> peut \u00eatre vu comme un bout de code natif, optimis\u00e9 pour votre OS \/ architecture CPU, que la JVM va substituer \u00e0 l&rsquo;impl\u00e9mentation Java de la m\u00e9thode selon certaines conditions.<\/p>\n<h2>Conclusion<\/h2>\n<p>Attention \u00e0 ce que vous mesurez, l&rsquo;impl\u00e9mentation via une <code>String<\/code> ne g\u00e9n\u00e8re pas le m\u00eame tableau de bytes que les autres, et est donc beaucoup moins performante.<\/p>\n<p>La r\u00e9utilisation d&rsquo;un <code>ByteBuffer<\/code> n&rsquo;est pas toujours la meilleure solution, car le co\u00fbt de recyclage n&rsquo;est pas anodin. Les allocations sont peu co\u00fbteuses au sein de la JVM, et parfois il vaut mieux allouer qu&rsquo;ex\u00e9cuter plus d&rsquo;instructions.<\/p>\n<p>Follow the force, read the code ;)<\/p>\n<p>Bien que JMH soit un super outil, il faut des comp\u00e9tences techniques et beaucoup de temps pour analyser totalement ses r\u00e9sultats. M\u00eame si les diff\u00e9rences constat\u00e9es ne sont pas totalement expliqu\u00e9es; je suis quand m\u00eame content de ma petite exp\u00e9rimentation ;)<\/p>","protected":false},"excerpt":{"rendered":"<p>J&rsquo;utilise beaucoup Kafka ces derniers temps, et dans Kafka, beaucoup de choses sont des tableaux de bytes, m\u00eame les headers ! Comme j&rsquo;ai de nombreux composants qui s&rsquo;\u00e9changent des messages, j&rsquo;ai ajout\u00e9 des headers pour aider au suivi des messages, et entre autres un header timestamp qui a comme valeur System.currentTimeMillis(). Il m&rsquo;a donc fallut transformer un long en tableau de byte; d&rsquo;une mani\u00e8re tr\u00e8s na\u00efve, j&rsquo;ai cod\u00e9 \u00e7a : String.valueOf(System.currentTimeMillis()).getBytes(). Mais instancier une String \u00e0 chaque cr\u00e9ation de header&#8230;<p class=\"read-more\"><a class=\"btn btn-default\" href=\"https:\/\/www.loicmathieu.fr\/wordpress\/fr\/informatique\/benchmark-conversion-de-long-en-byte\/\">Lire la suite<span class=\"screen-reader-text\"> Lire la suite<\/span><\/a><\/p><\/p>","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"jetpack_post_was_ever_published":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"activitypub_content_warning":"","activitypub_content_visibility":"","activitypub_max_image_attachments":4,"activitypub_interaction_policy_quote":"anyone","activitypub_status":"","footnotes":"","jetpack_publicize_message":"","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":true,"jetpack_social_options":{"image_generator_settings":{"template":"highway","default_image_id":0,"font":"","enabled":false},"version":2}},"categories":[9],"tags":[178,11,188,189,159],"class_list":["post-1180","post","type-post","status-publish","format-standard","hentry","category-informatique","tag-benchmark","tag-java","tag-jmh","tag-micro-benchmark","tag-performance"],"aioseo_notices":[],"jetpack_publicize_connections":[],"jetpack_featured_media_url":"","jetpack_sharing_enabled":true,"jetpack_likes_enabled":true,"jetpack-related-posts":[{"id":1330,"url":"https:\/\/www.loicmathieu.fr\/wordpress\/fr\/informatique\/devoxx-france-2021-ledition-9-3-4\/","url_meta":{"origin":1180,"position":0},"title":"Devoxx France 2021 &#8211; l&rsquo;\u00e9dition 9 3\/4","author":"admin","date":"vendredi  1 octobre 2021","format":false,"excerpt":"Cette semaine, c'est Devoxx France. Et pour la premi\u00e8re fois depuis pas mal de temps, je sors de chez moi, et j'y vais ! Je vous \u00e9cris ces mots dans le train de retour de la deuxi\u00e8me journ\u00e9e, pas de troisi\u00e8me pour moi cette ann\u00e9e. J'ai assist\u00e9 \u00e0 quelques talks,\u2026","rel":"","context":"Dans &quot;informatique&quot;","block_context":{"text":"informatique","link":"https:\/\/www.loicmathieu.fr\/wordpress\/fr\/category\/informatique\/"},"img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":1030,"url":"https:\/\/www.loicmathieu.fr\/wordpress\/fr\/informatique\/for-vs-stream\/","url_meta":{"origin":1180,"position":1},"title":"For vs Stream","author":"admin","date":"mardi 21 avril 2020","format":false,"excerpt":"Cela faisait longtemps que je n'avais pas \u00e9crit un article de blog et pourtant, pour cause de confinement, j'ai du temps pour le faire! N'ayant pas d'id\u00e9e, j'ai demand\u00e9 \u00e0 ma twitosph\u00e8re de m'en donner, et j'ai eu une r\u00e9ponse int\u00e9ressante : Les diff\u00e9rences de perf entre stream et for\u2026","rel":"","context":"Dans &quot;informatique&quot;","block_context":{"text":"informatique","link":"https:\/\/www.loicmathieu.fr\/wordpress\/fr\/category\/informatique\/"},"img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":966,"url":"https:\/\/www.loicmathieu.fr\/wordpress\/fr\/informatique\/1-an-chez-zenika\/","url_meta":{"origin":1180,"position":2},"title":"1 an chez Zenika","author":"admin","date":"mardi  3 septembre 2019","format":false,"excerpt":"Aujourd'hui est un jour sp\u00e9cial, cela fait un an que je suis arriv\u00e9 chez Zenika, apr\u00e8s 9 ans en tant qu'architecte logiciel dans la DSI d'un grand groupe de retail. Et apr\u00e8s un an, quel est donc le bilan ? Tout d'abord, Zenika m'a surpris. Lors des \u00e9changes que j'ai\u2026","rel":"","context":"Dans &quot;informatique&quot;","block_context":{"text":"informatique","link":"https:\/\/www.loicmathieu.fr\/wordpress\/fr\/category\/informatique\/"},"img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":1309,"url":"https:\/\/www.loicmathieu.fr\/wordpress\/fr\/informatique\/3-ans-chez-zenika\/","url_meta":{"origin":1180,"position":3},"title":"3 ans chez Zenika","author":"admin","date":"mardi  7 septembre 2021","format":false,"excerpt":"Avec un peu de retard, voici le bilan de ma troisi\u00e8me ann\u00e9e chez Zenika. Pour ceux qui seraient int\u00e9ress\u00e9 par ce que j'avais fait l'ann\u00e9e pr\u00e9c\u00e9dente, c'est ici : Ma deuxi\u00e8me ann\u00e9e chez Zenika. Quelques chiffres : 7 articles sur mon blog perso, 2 dans le magazine Programmez et 2\u2026","rel":"","context":"Dans &quot;informatique&quot;","block_context":{"text":"informatique","link":"https:\/\/www.loicmathieu.fr\/wordpress\/fr\/category\/informatique\/"},"img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":1138,"url":"https:\/\/www.loicmathieu.fr\/wordpress\/fr\/informatique\/ma-deuxieme-annee-chez-zenika\/","url_meta":{"origin":1180,"position":4},"title":"Ma deuxi\u00e8me ann\u00e9e chez Zenika","author":"admin","date":"jeudi  3 septembre 2020","format":false,"excerpt":"Aujourd\u2019hui est un jour sp\u00e9cial, cela fait deux ans que je suis arriv\u00e9 chez Zenika, apr\u00e8s 9 ans en tant qu\u2019architecte logiciel dans la DSI d\u2019un grand groupe de retail. L'ann\u00e9e derni\u00e8re j'avais fait le bilan de ma premi\u00e8re ann\u00e9e pass\u00e9e : 1 an chez Zenika. Et un an apr\u00e8s,\u2026","rel":"","context":"Dans &quot;informatique&quot;","block_context":{"text":"informatique","link":"https:\/\/www.loicmathieu.fr\/wordpress\/fr\/category\/informatique\/"},"img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":39,"url":"https:\/\/www.loicmathieu.fr\/wordpress\/fr\/informatique\/sitemesh-gerer-son-layout-sans-douleur\/","url_meta":{"origin":1180,"position":5},"title":"Sitemesh : g\u00e9rer son layout sans douleur","author":"admin","date":"vendredi  6 juillet 2007","format":false,"excerpt":"Sitemesh permet de g\u00e9rer facilement le layout des application web JAVA. Une petite description de comment il marche.","rel":"","context":"Dans &quot;informatique&quot;","block_context":{"text":"informatique","link":"https:\/\/www.loicmathieu.fr\/wordpress\/fr\/category\/informatique\/"},"img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]}],"_links":{"self":[{"href":"https:\/\/www.loicmathieu.fr\/wordpress\/fr\/wp-json\/wp\/v2\/posts\/1180","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.loicmathieu.fr\/wordpress\/fr\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.loicmathieu.fr\/wordpress\/fr\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.loicmathieu.fr\/wordpress\/fr\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.loicmathieu.fr\/wordpress\/fr\/wp-json\/wp\/v2\/comments?post=1180"}],"version-history":[{"count":0,"href":"https:\/\/www.loicmathieu.fr\/wordpress\/fr\/wp-json\/wp\/v2\/posts\/1180\/revisions"}],"wp:attachment":[{"href":"https:\/\/www.loicmathieu.fr\/wordpress\/fr\/wp-json\/wp\/v2\/media?parent=1180"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.loicmathieu.fr\/wordpress\/fr\/wp-json\/wp\/v2\/categories?post=1180"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.loicmathieu.fr\/wordpress\/fr\/wp-json\/wp\/v2\/tags?post=1180"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}