{"id":338,"date":"2008-09-13T14:07:42","date_gmt":"2008-09-13T02:07:42","guid":{"rendered":"https:\/\/www.deltics.co.nz\/blog\/?p=338"},"modified":"2009-08-06T13:57:25","modified_gmt":"2009-08-06T01:57:25","slug":"delphi-2009-a-heads-up-for-low-level-coders","status":"publish","type":"post","link":"https:\/\/www.deltics.co.nz\/blog\/posts\/338\/","title":{"rendered":"Delphi 2009 &#8211; A Heads-Up for Low-Level Coders"},"content":{"rendered":"<span class=\"rt-reading-time\" style=\"display: block;\"><span class=\"rt-label rt-prefix\">[Estimated Reading Time: <\/span> <span class=\"rt-time\">8<\/span> <span class=\"rt-label rt-postfix\">minutes]<\/span><\/span><p>Prompted by a conversation with some colleagues where-in we collectively speculated about the implementation details of a generic class and what impact &#8211; if any &#8211; this might have on performance vs a &#8220;traditional&#8221; polymorphic equivalent, I threw together a quick performance test case in my Smoketest framework, and as a result discovered a couple of significant changes in Delphi 2009 that created some unexpected problems.<\/p>\n<p><!--more--><\/p>\n<h2>1. FastStrings<\/h2>\n<p>As I feared, the <a href=\"http:\/\/www.koders.com\/delphi\/fidFB386C5C240FD5E72013C882ADD7600FDF60E6C7.aspx?s=socket\" target=\"_blank\">immensely useful FastStrings library<\/a> is broken by the switch to Unicode.\u00a0 Bye-bye <strong>FastCharPos<\/strong>, <strong>FastReplace<\/strong> etc etc.<\/p>\n<p>This is going to be a major headache for me in my day-job as I work on a 2 million line code base that makes extensive use of <strong>FastStrings<\/strong>; string performance is critical to the overall performance of the application as a whole (just one reason we are somewhat wary about the universal nature of the Unicode change in Delphi 2009).<\/p>\n<p>In the lead up to the Delphi 2009 release I repeatedly asked for some indication of what impact the Unicode transition would have on <strong>FastStrings<\/strong> and none was ever forthcoming.<\/p>\n<p>I guess I have my answer now.<\/p>\n<p>As I understand it, work on the library stopped some time ago.\u00a0 I will need to try to get in touch with <strong>Peter Morris<\/strong> to see if he has any plans at all to return to it and update it for Delphi 2009 or if he is aware of anyone that is.<\/p>\n<p>I don&#8217;t know at this stage what &#8211; if anything &#8211; can be done to make <strong>FastStrings<\/strong> usable with Delphi 2009, other than changing the declarations in the library to <strong>AnsiString<\/strong> so that the compiler won&#8217;t let you call them with <strong>UnicodeString<\/strong> (currently they are declared as just <strong>String<\/strong>, which of course now <span style=\"text-decoration: underline;\">is<\/span> <strong>UnicodeString<\/strong>).<\/p>\n<p>I&#8217;ve done some initial testing of string performance in Delphi 2009 and have actually been pleasantly surprised so far.\u00a0 Simple string concatenation of <strong>UnicodeString<\/strong> seems for some reason to be actually faster than <strong>ANSIStrings<\/strong>, and faster even than <strong>ANSIString<\/strong> handling in Delphi 7 (with FastMM).<\/p>\n<p>I find this puzzling given that I was sure I had seen the new <a href=\"http:\/\/blogs.codegear.com\/andreanolanusse\/2008\/07\/24\/tiburon-building-strings-with-tstringbuilder\/\" target=\"_blank\"><strong>TStringBuilder<\/strong> class<\/a> being recommended for performance reasons.\u00a0 For simple string concentation at least, the exact opposite appears to be the case.<\/p>\n<p><strong>ANSIString<\/strong> performance on the other hand I think will suffer quite badly in Delphi 2009, most &#8211; if not all &#8211; of which I think is the result of an assumption of Unicode in the RTL.<\/p>\n<p>There is no <strong>ANSIString<\/strong> version of <strong>IntToStr()<\/strong> for example &#8211; there is only one <strong>IntToStr()<\/strong> and it returns <strong>String<\/strong>, i.e. a <strong>UnicodeString<\/strong>.\u00a0 This means that use of <strong>IntToStr()<\/strong> (or any of a number of common RTL string routines) when working with <strong>ANSIString<\/strong> variables will inevitably incurr ANSI\/Unicode conversions that previously they did not.<\/p>\n<p>With this assumption propogated throughout the RTL, using <strong>ANSIString<\/strong> in a Delphi 2009 application could actually prove costly if string performance is a concern.<\/p>\n<p>I don&#8217;t quite understand why CodeGear could not have provided ANSI versions of these RTL routines (<strong>IntToStrA<\/strong>, for example) alongside the newly Unicode capable &#8220;default&#8221; implementations.<\/p>\n<h2>2. News FLASH!\u00a0 The VMT Has Changed<\/h2>\n<p>After fixing compilation problems with <strong>FastStrings<\/strong> (i.e. removing <strong>FastStrings<\/strong> from the code!) upon attempting to run my performance test case I immediately ran into an access violation in the code that identifies the published methods in my test case classes.<\/p>\n<p>The reason appears to be that the layout of the VMT (Virtual Method Table) has changed.<\/p>\n<p>The help files proved to be rather begrudging in giving up their secrets in this area.\u00a0 The <strong>Class Types<\/strong> topic itself that describes the VMT layout clearly has not been updated (and took some finding, being now a section buried in an <strong>Internal Data Formats<\/strong> topic) as it still describes the previous VMT layout.<\/p>\n<p>Ironically the deprecated <strong>vmt<em>XXX<\/em><\/strong> constants <em><span style=\"text-decoration: underline;\">do<\/span><\/em> seem to have been updated &#8211; these deprecated constants seem to include some all new, instantly deprecated ones.<\/p>\n<p>So for example I can see that <strong>vmtEquals<\/strong> (a new entry at -44) occupies the position in the VMT that previously contained a pointer to the class name, and the entry that used to contain the instance size is now occupied by a pointer to the <strong>GetHashCode<\/strong> method (<strong>vmtGetHashCode<\/strong>).<\/p>\n<p>As far as I can tell from the various new <strong>vmtXXX<\/strong> constants the changes are three additional method pointers inserted between the <strong>vmtParent <\/strong>and <strong>vmtSafecallException <\/strong>entries:<\/p>\n<pre class=\"delphi\">    Equals\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 : Pointer;\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \/\/ -44\r\n    GetHashCode\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 : Pointer;\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \/\/ -40\r\n    ToString\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 : Pointer;\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \/\/ -36<\/pre>\n<p>So all offsets previously at -36 and above (below?) are affected.\u00a0 There are also some new initial entries in the &#8220;user&#8221; virtual methods area, i.e. at <em>positive<\/em> offsets:<\/p>\n<pre class=\"delphi\">    QueryInterface    : Pointer;                \/\/ 0\r\n    AddRef            : Pointer;                \/\/ +4\r\n    Release           : Pointer;                \/\/ +8\r\n    CreateObject      : Pointer;                \/\/ +12<\/pre>\n<p>Now obviously the VMT is an internal implementation detail of Delphi, and everyone knows that relying on it&#8217;s layout is dangerous &#8211; I even have a warning to myself to this effect in my own code!<\/p>\n<p>So don&#8217;t mistake this for a complaint at the layout having changed &#8211; it&#8217;s just a heads-up to anyone who themselves may have used the previous documentation and\/or other works describing the previous VMT layout (which up to now has proven remarkably stable) and a caution that the Delphi 2009 documentation itself does not appear to have been completely updated.<\/p>\n<p>For my own routines using the VMT, that wasn&#8217;t the end of the story&#8230;.<\/p>\n<h2>3. PointerMath<\/h2>\n<p>Delphi 2009 now supports pointer math on all pointer types!  YAY!\u00a0 \ud83d\ude42<\/p>\n<p>Seriously &#8211; this is HUGE!<\/p>\n<p>Previously pointer math was only supported on <strong>PChar<\/strong> pointers.  You could of course <strong>Inc()<\/strong> any typed pointer by the size of the type pointed to, but actual addition of an arbitrary offset (among other things) simply wasn&#8217;t allowed.\u00a0 So to increment a pointer you would have to write something like, e.g:<\/p>\n<pre class=\"delphi\">  ptr := Pointer(Cardinal(ptr) + offset);<\/pre>\n<p>Which is cumbersome to say the least.  So instead I was naively exploiting the fact that <strong>PChar<\/strong> was a pointer to a single byte-size data type:<\/p>\n<pre class=\"delphi\">  Inc(PChar(ptr), offset);<\/pre>\n<p>Can you see the problem?<\/p>\n<p><strong>PChar<\/strong> in Delphi 2009 is no longer a pointer to a single byte-sized Char, it&#8217;s a pointer to a <strong>WideChar<\/strong>, which of course is TWO bytes.  Oops.<\/p>\n<p>&#8220;Aha!&#8221; I thought &#8220;Pointer math to the rescue?&#8221;<\/p>\n<p>The new pointer math capability is subject to a compiler switch (is OFF by default) so I could now write:<\/p>\n<pre class=\"delphi\">  {$pointermath ON}\r\n  ptr := ptr + offset;\r\n  {$pointermath OFF}<\/pre>\n<p>But there are two problems with this.<\/p>\n<p>First, if <strong>ptr<\/strong> is a typed pointer then the resulting increment of <strong>ptr<\/strong> is <strong>offset * type size<\/strong>. In other words, this is exactly equivalent to a call that you could previously have made using <strong>Inc()<\/strong>.\u00a0 This is perhaps to be expected, but it might catch you out &#8211; as it initially did me.<\/p>\n<p>The second problem is this of course won&#8217;t compile in older versions of Delphi; wrapping all the necessary conditionals and compiler options to enable compatibility with those older compilers whilst preserving the state of the switch in any current project quickly gets out of hand, and in any case I will still need old-style pointer adjustment code for those older compilers.<\/p>\n<p>So in the end I just changed my <strong>PChar<\/strong> to a <strong>PByte<\/strong> and all was again right with the world.<\/p>\n<pre class=\"delphi\">  Inc(PByte(ptr), offset);<\/pre>\n<p>Just another example of &#8220;say what you mean if you really mean it&#8221;.<\/p>\n<p>In this case, I want to increment in byte-size chunks, so use a <strong>PByte<\/strong> already!\u00a0 Not some other pointer that happened &#8211; at the time &#8211; to also point to a one-byte size data type.<\/p>\n<p>Pointer math is undeniably a useful addition to the language, but when it comes to advancing typed pointers I&#8217;d suggest sticking to <strong>Inc()<\/strong> calls &#8211; it&#8217;s portable between Delphi versions, is directly equivalent to the new alternative, but will be more familiar to the widest audience since the <strong>Inc()<\/strong> behaviour is more likely to be already understood where plain addition of ordinal values to pointer types is new and therefore not so familiar.<\/p>\n<p>It&#8217;s also far cleaner to coerce byte offsets from an <strong>Inc()<\/strong> call (where required) than it is using pointer math:<\/p>\n<pre class=\"delphi\">  Inc(PByte(ptr), offset);<\/pre>\n<p>vs<\/p>\n<pre class=\"delphi\">  ptr := PValueType(PByte(ptr) + offset);<\/pre>\n<h2>4. The Frustation of the New<\/h2>\n<p>Pointer Math does again highlight an inevitable frustration with new language features.<\/p>\n<p>Apart from the simple job of learning how the new features behave in code, the ability to use them in code destined (or at least intended) to be shared with the wider Delphi community is severely limited by the uptake of the latest version of Delphi.\u00a0 At the very least it means having to wait some time before incorporating them in code until the latest version has gained traction in the community, and even then you either have to duplicate code subject to compilation directives or close the door on users of older versions of Delphi.<\/p>\n<p>This has always been, and will always be, a problem.\u00a0 There is no easy answer.<\/p>\n<p>But in the case of Delphi 2009 that problem is exacerbated in my view by the fact that Unicode is so tightly coupled to Delphi 2009.\u00a0 The likelihood that Delphi 2009 will become the ubiquitous Delphi version any time soon is &#8211; imho &#8211; going to be limited by the fact that anyone wishing or needing to maintain ANSI applications are likely to be running Delphi 2009 alongside their older Delphi compilers.<\/p>\n<h2>5. Generics are FAST!<\/h2>\n<p>I eventually fixed my problems and got my performance test case running.<\/p>\n<p>It was not an exhaustive test, I simply drafted two routines that added the integers 1 thru 1000 to an integer list and then iterates over the list, summing the items in the list.<\/p>\n<p>Two different integer list classes were used and the performance of the code in each case compared (for brevity a lot of implementation code is not shown):<\/p>\n<pre class=\"delphi\">    TGenericIntegerList = Generics.Collections.TList;\r\n\r\n    TDerivedIntegerList = class(Classes.TList)\r\n    private\r\n      function get_Item(const aIndex: Integer): Integer;\r\n    public\r\n      procedure Add(const aInteger: Integer);\r\n      property Items[const aIndex: Integer]: Integer read get_Item; default;\r\n    end;\r\n\r\n    TTestGenerics = class(TPerformanceCase)\r\n      procedure GenericList;\r\n      procedure DerivedList;\r\n      procedure EnumGenericList;\r\n      procedure EnumDerivedList;\r\n    end;\r\n\r\nbegin\r\n  TestSuite.Initialize;\r\n  TTestGenerics.Create(2, pmSeconds);\r\n  TestSuite.Ready;\r\nend.<\/pre>\n<p>The test case is configured to run repeatedly for 2 seconds &#8211; Smoketest then reports the number of complete executions of each method in that period.<\/p>\n<p>Each of the test case methods follows the same basic pattern.  The GenericList method is shown here:<\/p>\n<pre class=\"delphi\">var\r\n  i: Integer;\r\n  r: Integer;\r\n  list: TGenericIntegerList;\r\nbegin\r\n  r := 0;\r\n\r\n  list := TGenericIntegerList.Create;\r\n  try\r\n    list.Capacity := 1000;\r\n\r\n    for i := 1 to 1000 do\r\n      list.Add(i);\r\n\r\n    for i := 0 to Pred(list.Count) do\r\n      r := r + list.Items[i];\r\n  finally\r\n    list.Free;\r\n  end;<\/pre>\n<p>The <strong>EnumGenericList<\/strong> method replaced the explicit iteration of the list with an enumerator based version, i.e:<\/p>\n<pre class=\"delphi\">    for i in list do\r\n      r := r + i;<\/pre>\n<p>This had nothing to do with the performance of generics as such but was more out of curiosity, as I had a suspicion that I would see a difference.\u00a0 The enumerator test for the derived list implementation was implemented for completeness, but frankly you wouldn&#8217;t enumerate over such an implementation since &#8211; out of the box at least &#8211; the enumerator for that class yields <strong>Pointers<\/strong>, not <strong>Integer<\/strong>, so some real nasty type-casting is needed.<\/p>\n<p>The results were good news for generics, not so good for enumerators.<\/p>\n<p>The generics based implementation of a <strong>TList<\/strong>, in this performance test case, was consistently at least 50% faster than the implementation derived from a regular <strong>TList<\/strong>.<\/p>\n<p>A 50% improvement is something absolutely <span style=\"text-decoration: underline;\"><strong>not<\/strong><\/span> to be sniffed at.<\/p>\n<p>Unfortunately the enumerator implementations aren&#8217;t quite so rewarding.  Admittedly the test cases don&#8217;t completely isolate the list iteration code for a true test of manual iterations vs enumerators, but nevertheless, in this case the enumerator based test methods were at least 25% slower than their manual iteration counterparts.<\/p>\n<p>For completeness I should mention that the performance impact of an enumerator was actually slightly worse for the derived class implementation.<\/p>\n<p>On the face of it, this would seem to confirm something that I have always suspected with enumerators &#8211; the syntactic sugar they provide comes at a not insignificant performance cost and I&#8217;ve personally never considered the syntactic sugar as earth-shatteringly significant as some.<\/p>\n<p>It should be said that in a very large proportion of applications the performance impact is likely to be insignificant, but it is something that developers should perhaps be aware of when working in performance critical areas.<\/p>\n<p>At the same time, it does seem that generics deliver <span style=\"text-decoration: underline;\"><strong>more<\/strong><\/span> than just syntactic sugar.\u00a0 I am frankly stunned by the performance improvement that they seem to bring.<\/p>\n<p>It makes it even more of a shame that the implementation in Delphi 2009, as it stands currently, lacks operator constraints.\u00a0 This demands a great deal of <a href=\"http:\/\/blogs.teamb.com\/craigstuntz\/2008\/09\/09\/37833\" target=\"_blank\">hoop jumping and circumlocutory tricks <\/a>to create generic classes that are anything more than simple containers.<\/p>\n<p>This problem is not confined to Delphi though &#8211; .NET and C# generics have similar problems.<\/p>\n<p>Hopefully these shortcomings will be addressed in the future (and perhaps an opportunity for Delphi to steal a march on C#).\u00a0 But for now, the prospect of containers that are not only type-safe but also more efficient is enough to get me a lot more excited about generics than I &#8211; for one &#8211; previously was.<\/p>\n","protected":false},"excerpt":{"rendered":"<p><span class=\"rt-reading-time\" style=\"display: block;\"><span class=\"rt-label rt-prefix\">[Estimated Reading Time: <\/span> <span class=\"rt-time\">8<\/span> <span class=\"rt-label rt-postfix\">minutes]<\/span><\/span> Prompted by a conversation with some colleagues where-in we collectively speculated about the implementation details of a generic class and what impact &#8211; if any &#8211; this might have on performance vs a &#8220;traditional&#8221; polymorphic equivalent, I threw together a quick performance test case in my Smoketest framework, and as a result discovered a couple [&hellip;]<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"jetpack_post_was_ever_published":false,"jetpack_publicize_message":"","jetpack_is_tweetstorm":false,"jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":false,"jetpack_social_options":[]},"categories":[4,7],"tags":[292,59,60,21,51,293],"jetpack_publicize_connections":[],"jetpack_featured_media_url":"","jetpack_shortlink":"https:\/\/wp.me\/p1TKYv-5s","jetpack_sharing_enabled":true,"jetpack-related-posts":[{"id":375,"url":"https:\/\/www.deltics.co.nz\/blog\/posts\/375\/","url_meta":{"origin":338,"position":0},"title":"Delphi 2009 &#8211; StringPerformance Redux","date":"22 Sep 2008","format":false,"excerpt":"It looks like I may have jumped the gun with my conclusions from the previous exercise to benchmark string performance in Delphi 2009.\u00a0 Following a useful exchange in the comments with Kryvich I corrected a small discrepancy in the tests and made some changes to the performance testing subsystem within\u2026","rel":"","context":"In &quot;Delphi&quot;","img":{"alt_text":"","src":"https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/delphi2009-stringperformance-chart.jpg?resize=350%2C200&ssl=1","width":350,"height":200},"classes":[]},{"id":284,"url":"https:\/\/www.deltics.co.nz\/blog\/posts\/284\/","url_meta":{"origin":338,"position":1},"title":"This Week&#8217;s Poll, Delphi at Tech Ed and 2009 Ship Date","date":"01 Sep 2008","format":false,"excerpt":"Since I'm preparing a series of posts about (and eventual publication of) Smoketest, my own testing framework, I thought I'd test the water with this weeks poll and see what - if anything - people are already using as far as unit testing goes. Also I thought I'd briefly mention\u2026","rel":"","context":"In &quot;Delphi&quot;","img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":349,"url":"https:\/\/www.deltics.co.nz\/blog\/posts\/349\/","url_meta":{"origin":338,"position":2},"title":"Delphi 2009 &#8211; String Performance","date":"18 Sep 2008","format":false,"excerpt":"NOTE: Downloads are now fixed! Andreas Hausladen generously took the time to make some detailed comments on my previous post, one of which prompted me to throw together some further performance test cases for String types specifically.\u00a0 The results were something of a mixed bag and contained some surprises. The\u2026","rel":"","context":"In &quot;Delphi&quot;","img":{"alt_text":"","src":"https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/delphi2009-stringperformance-resultscapture.jpg?resize=350%2C200&ssl=1","width":350,"height":200},"classes":[]},{"id":2185,"url":"https:\/\/www.deltics.co.nz\/blog\/posts\/2185\/","url_meta":{"origin":338,"position":3},"title":"Smoketest &#8211; Performance Case Visualisations","date":"22 Nov 2013","format":false,"excerpt":"This post is a peek behind the curtain of the next major update to Smoketest which I hope to have completed shortly: Performance Case visualisations. Smoketest has always had two types of test case that you could implement by deriving from two distinct base classes: TTestCase is the base class\u2026","rel":"","context":"In &quot;Delphi&quot;","img":{"alt_text":"","src":"https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/Screen-Shot-2013-11-22-at-09.57.52.png?resize=350%2C200&ssl=1","width":350,"height":200},"classes":[]},{"id":2144,"url":"https:\/\/www.deltics.co.nz\/blog\/posts\/2144\/","url_meta":{"origin":338,"position":4},"title":"Smoketest 1.0 &#8211; Release and Be Damned!","date":"14 Nov 2013","format":false,"excerpt":"As I have been promising for some time (quite literally 5 years (!), I am ashamed to admit) I am finally unclenching and releasing the Smoketest framework into the wild, ready or not. The code is published and will continue to be updated in a github repository. Documentation is still\u2026","rel":"","context":"In &quot;Delphi&quot;","img":{"alt_text":"","src":"https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/Screen-Shot-2013-11-14-at-19.51.57-.png?resize=350%2C200&ssl=1","width":350,"height":200},"classes":[]},{"id":2095,"url":"https:\/\/www.deltics.co.nz\/blog\/posts\/2095\/","url_meta":{"origin":338,"position":5},"title":"Extending Smoketest (Part 1) &#8211; An Inspector Calls","date":"05 Nov 2013","format":false,"excerpt":"In the soon to be released Smoketest framework it is sometimes useful to create new test types to supplement the tests built-in to the framework. In this and the next post I will walk through the process of implementing and registering a custom test with the Smoketest framework. In a\u2026","rel":"","context":"In &quot;Delphi&quot;","img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]}],"_links":{"self":[{"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/posts\/338"}],"collection":[{"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/comments?post=338"}],"version-history":[{"count":11,"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/posts\/338\/revisions"}],"predecessor-version":[{"id":459,"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/posts\/338\/revisions\/459"}],"wp:attachment":[{"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/media?parent=338"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/categories?post=338"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/tags?post=338"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}