7

In sx.el, we have a case where we need to verify if we've passed GET or POST as an argument.

Currently we have the argument passing as a string and are using (string= "GET" url-method) to compare it to "GET".

Is there any elisp/byte-compilation advantage to changing it to a symbol (equal url-method 'GET)?

Jonathan Leech-Pepin
  • 4,307
  • 1
  • 19
  • 32
  • Regardless of speed, symbols are idiomatic for this purpose in Lisp. Besides, use `eq` to compare against a symbol. –  Nov 27 '14 at 18:36
  • `eq` will convert Emacs objects to `int` and compare them. Comparing `int`s is much faster than comparing `string`s. – abo-abo Nov 27 '14 at 19:13
  • 1
    `string=` will work as expected whether `url-method` is a symbol or a string. OTOH if you use `eq` or `equal`, you have to make sure the arguments are of the same type. – YoungFrog Jul 03 '16 at 06:28

2 Answers2

11

New users of Lisp who come from other languages sometimes use strings for such purposes out of habit.

Symbols can be compared with eq instead of just equal. string= uses code similar to equal, to test each character until a mismatch is found. So yes, symbol comparison can be slightly faster. If you are using the comparison in a loop, for instance, the difference might matter.

In addition, depending on the particular code, it can often make the code simpler or more readable to use symbols instead of strings. And there are functions (and macros etc.) that expect symbols or that compare things using eq (or eql) by default. To use those with strings you need to anyway convert to symbols.

Drew
  • 75,699
  • 9
  • 109
  • 225
7

Note that the lisp reader interns symbols, such that independent references to a given symbol give you the exact same lisp object, and consequently you're able to compare them with eq (which only needs to compare the object addresses).

Conversely, independent strings are always different lisp objects, and therefore you have to compare their contents.

You would therefore expect the eq comparison to win for performance.

Curiously enough (I'm certainly very surprised), some trivial testing with benchmark-run is giving string= the win by quite a margin. This seems very odd to me. YMMV?


Edit: So I just noticed this answer (and its comment) again, and felt inspired to see if I could recreate and explain the result.

n.b. Nothing is byte-compiled initially.

The first realisation was that my symbols were quoted and the strings were not, and I immediately found that the quote was responsible for bulk of the speed difference:

(benchmark-run 10000000
  (string= "foo" "foo"))

is, for smallish strings, consistently quicker than:

(benchmark-run 10000000
  (eq 'foo 'foo))

however, if we also quote the string:

(benchmark-run 10000000
  (string= '"foo" '"foo"))

that almost evens things out entirely.

However, on average, unless the string is quite large, the string comparison still seems to win by a hair.

An alternative approach is to bind the objects to variables:

(let ((foo 'test)
      (bar 'test))
  (benchmark-run 10000000
    (eq foo bar)))

(let ((foo "test")
      (bar "test"))
  (benchmark-run 10000000
    (string= foo bar)))

Once again, I'm seeing the string comparison faster by a nose, on average.

After byte-compilation, I'm only seeing a consistent result for the let-bound variable cases, where eq is consistently faster than string= (taking about 2/3 of the time).

For the other examples I'm getting nonsensical (to me) outcomes like the quoted strings being quicker than the unquoted strings, which I can only guess is effectively noise from other aspects of the execution and/or benchmark-run itself. There was enough variety between different runs of the same function to entirely obscure any differences between runs of different functions.


My conclusions are:

(a) eq comparisons with a symbol can (somewhat counter-intuitively) be slower than a string comparison, if the symbol is quoted.

(b) Unless the strings are rather large, the practical differences are utterly negligible, and so I wouldn't bother converting one to the other for purely performance reasons.

phils
  • 48,657
  • 3
  • 76
  • 115
  • 3
    Possibly because the way you've set up your test causes each symbol to be generated afresh in each run of the loop, so you're pitting `intern`+`eq` against `string=`. Or possibly for some completely different reason: it's impossible to tell without seeing your code. You should post your test code as a question on this site. – Gilles 'SO- stop being evil' Nov 28 '14 at 18:57
  • I tried using `benchmark-run-compiled` (with the idea that that would remove the cost of `intern`), but several times got negative result times. I think both operations are too fast to measure reliably. – npostavs Jul 03 '16 at 17:59