I, personally, would expect an START TRANSACTION to burn an XID, they are serial, and they need to be allocated to have transaction ordering, like the thing which happens with the sequences. I assume the server can have some optimizations ( like delaying XID adquisition to the first appropiate statement, which I think depends on your isolation level ), but I would never expect it to not allocate it before an insert, it needs it to be sent to the table, in case it succeeds, and has to acquire it beforehand, in case someone needs to acquire another xid between the time it starts inserting and the time it succeeds or fail. Some internals expert may shed some light, but after reading your link it seems your problem is just you do too many transactions without a vacuum ( also reading your pointed threas it sees you do vacuum fulls, which seems unneeded ) and expecting postgres has some kind of magic to avoid burning the xids.
The issue is that the following uses 5 XIDs when I would only expect it to us 1:
It appears that the unique violation that is caught and ignored increments the XID even though I didn't expect that to happen. I agree that our software was burning XIDs needlessly and Postgres handled this situation as best as it could. It also sounds like Postgres 9.5 adds features to support this sort of use more efficiently, but the XID incrementing on the unique violation seems like it could/should be fixed, if it hasn't been already.