14 ms·
Common Lisp programs run by default in a way that calls to undefined functions are detected. Here the Lisp simply tries to look up the function object from the
by _19qg 3y ago
Common Lisp programs run by default in a way that calls to undefined functions are detected.
Here the Lisp simply tries to look up the function object from the symbol. There is no function, so it signals a condition (aka exception). The default exception handler gets called (without unwinding the stack). This handler prints the restarts and calls another REPL. I define the function -> the symbol now has a function definition. We then resume and Lisp tries again to get the function definition. The computation continues where we were.
That's the DEFAULT behavior you'll find in Common Lisp implementations.
- mathisfun123 3y ago>Common Lisp programs run by default in a way that calls to undefined functions are detected. Cool so what you're telling me is that by default every single function call incurs the unavoidable overhead of indirecting through some lookup for a function bound to a symbol. And you're proud of this?
- ghfwlc 3y agoCompared to Python, Common Lisp hardly has any performance issues.
- mathisfun123 3y agoOkay well when pytorch, tensorflow, pandas, Django, flask, numpy, networks, script, xgboost, matplotlib, spacy, scrapy, selenium get ported to lisp, I'll consider switching (only consider though since the are probably at least another 20 python python packages that I couldn't do my job without).
- svetlyak40wt 3y agoThat is a typical consumerism: "Give me everything ready to use and then I'll use it." How about bringing some value to the community?
- rwxrwxrwx 3y agoFor the sake of anyone reading this thread who isn't in the know: many of these libraries are really written in C/C++ and have Python bindings.
- mathisfun123 3y agoi said ported not implemented; the likelihood that any of those libraries sprout lisp bindings is about as likely as them being rewritten in lisp. so it's the same thing and the point is clear: i don't care about some zany runtime feature, i care about the ecosystem.
- rwxrwxrwx 3y agoStop moving the goalposts: your answer to a commenter who stated that Common Lisp was faster than Python (a fact) was a list of packages, many of which are (1) not even written in Python and (2) some of them actually do have Common Lisp bindings.
- lispm 3y agoI thought you know Lisp? Now you are surprised that Lisp often looks up functions via symbols -> aka "late binding"? How can that be? That's one of the basic Lisp features. Next you can find out what optimizing compilers do to avoid it, where possible or where wanted.
- mathisfun123 3y agoAt no point in time did I claim to know lisp well. I stated my familiarity at the outset. But what you all did was claim to know a lot about every other interpreted runtime without a grain of salt. >Next you can find out what optimizing compilers do to avoid it, where possible or where wanted. But compilers I am an expert in and what you're implying is impossible - either you have dynamic linkage, which means symbol resolution is deferred until call (and possibly guarded) or you have the equivalent of RTLD_NOW ie early/eager binding. There is no "optimization" possible here because the symbol is not Schrodinger's cat - it is either resolved statically or at runtime - prefetching symbols with some lookahead or cabinet is the same thing as resolving at calltime/runtime because you still need a guard.
- lispm 3y ago> But compilers I am an expert in and what you're implying is impossible > it is either resolved statically or at runtime Just tell Lisp which calls to statically resolve, inline, optimize. Overwrite the global default. (defun foo (a) (declare (inline +) (optimize (speed 3)) (type (integer 0 100) a)) (* 10 (+ 3 a))) Above tells Lisp to inline the + function, optimize for speed and declares the type of a to be an integer in the range 0 to 100. * (disassemble #'foo) ; disassembly for FOO ; Size: 32 bytes. Origin: #x7006DC8544 ; FOO ; 44: 40190091 ADD NL0, R0, #6 ; 48: 5C0180D2 MOVZ TMP, #10 ; 4C: 0A7C1C9B MUL R0, NL0, TMP ; 50: FB031AAA MOV CSP, CFP ; 54: 5A7B40A9 LDP CFP, LR, [CFP] ; 58: BF0300F1 CMP NULL, #0 ; 5C: C0035FD6 RET ; 60: E00120D4 BRK #15 ; Invalid argument count trap As you can see in the machine code, Lisp then uses the native machine code ADD and MUL instructions.
- 3y ago
- tsimionescu 3y agoDo you think Python or Ruby or PHP are any different? And yet, not one of them actually chose to use this in a sane way, where a simple lookup error doesn't have to crash the whole program.
- deleted 3y ago[deleted]
- User23 3y agoThat depends. The Common Lisp standard says nothing on the subject. CMUCL[1] and its descendent SBCL[2] do something clever called local call. It's not terribly difficult to optimize hot spots in your code to use local call. Outside of the bottlenecks, the full call overhead isn't significant for the overwhelming majority of cases. It's not like full call is any more expensive than a vtable lookup anyhow. [1] https://cmucl.org/downloads/doc/cmu-user-2010-05-03/compiler-hint.html#local-call https://cmucl.org/downloads/doc/cmu-user-2010-05-03/compiler... [2] https://www.sbcl.org/manual/#Miscellaneous-Efficiency-Issues https://www.sbcl.org/manual/#Miscellaneous-Efficiency-Issues
- kazinator 3y agoThis is not necessarily the case. Firstly, functions that are in the same compilation unit that refer to each other can use a faster mechanism, not going through a symbol. The same applies to lexical functions. Lisp compilers support inlining, and the spec allows automatic inlining between functions in the same compilation unit, and it allows calls to be less dynamic and m more optimized. If f and g are in the same file, where g calls f, then implementations are not required to allow f and go to be separately redefinable. So that is to say, if f is redefined only, the existing g may keep calling the old f. The intent is that redefinition has the granularity of compiled files: if a new version of the entire compiled file is loaded, then f and g get redefined together and all is cool. Lisp symbol lookup takes place at read time. If we are calling some function foo and have to go through the symbol (it's in another compilation unit), there is no hashing of the string "foo" going on at call time. The calling code hangs on to the foo symbol, which is an object. The hashing is done when the caller is loaded. The caller's compiled file contains literal objects, some of which are symbols. A compiled file on disk records externalized images of symbols which have the textual names; when those are internalized again, they become objects. The "classic" Lisp approach for implementing a global function binding of a symbol is be to have dedicated "function cell" field in the symbol itself. So, the compiled module from which the call is emanating is hanging on to the foo symbol as static data, and that symbol has a field in it (at a fixed offset) from which it can pull the current function object in order to call it (or use it indirectly). Cross-module Lisp calls have overhead due to the dynamism; that's a fact of life. You don't get safety for nothing. (Yes, yes, you can name ten "Lisp" implementations which do a hashed lookup on a string every time a function is called, I know.)
- chuchana 3y ago> If f and g are in the same file, where g calls f, then implementations are not required to allow f and go to be separately redefinable. So that is to say, if f is redefined only, the existing g may keep calling the old f. The intent is that redefinition has the granularity of compiled files: if a new version of the entire compiled file is loaded, then f and g get redefined together and all is cool. This isn't the default behaviour though, right?