From: Nathan T. <nb...@nb...> - 2013-10-18 19:17:46
|
I recently went through a library I'm working on and removed all type declarations on formal parameters from the top of DEFUNs, the reason being that I assumed such declarations were redundant in light of FTYPE declarations that also declare the types of the arguments passed to functions. And since function type declarations also declare return types, I figured it made more sense to keep the FTYPE declarations and remove the TYPE declarations from the DEFUNs. However, when I ran some tests after removing the declarations, I noticed that performance had taken a huge hit. Certain procedures took almost twice as long as they had taken before I had removed the type declarations. (The optimize setting being (optimize (debug 0) (safety 0) speed).) Can anyone here explain why this might be? (I figured that since optimizations aren't defined by the language itself, this would be the best place to ask.) Why don't FTYPE declarations on function arguments have the same effect as TYPE declarations on formal parameters? Given that the effect is different, as it seems, when _should_ one use FTYPE declarations for potential performance improvements? Should I be doing both if I want maximal performance? On a related note, if I declare a function's return type in an FTYPE declaration and bind a variable using the function, as in: (declaim (ftype (function (&rest) <type>)) foo) (let ((var (foo a b c))) ...) will the compiler infer that VAR has type <type>, or should I declare that at the top of the LET's body (assuming I want maximal performance)? Thank you. Nathan |
From: Paul K. <pv...@pv...> - 2013-10-18 20:26:56
|
In article <87o...@nb...>, Nathan Trapuzzano <nb...@nb...> wrote: > And since function type declarations also declare return > types, I figured it made more sense to keep the FTYPE declarations and > remove the TYPE declarations from the DEFUNs. There is a difference: FTYPE declarations describe how the function is called. In theory, this can make it difficult to specialise a function with FTYPEd information. SBCL and CMUCL are slightly more aggressive than strictly allowed by the standard and do propagate that information. So, in practice, for SBCL, the difference is tiny. > However, when I ran some tests after removing the declarations, I > noticed that performance had taken a huge hit. Certain procedures took > almost twice as long as they had taken before I had removed the type > declarations. (The optimize setting being (optimize (debug 0) (safety > 0) speed).) Can anyone here explain why this might be? One common issue is with assignment to formal parameters (and with default values for optional/keyword arguments sometimes). FTYPE declarations only describe calls to the function. Thus, even with SBCL's aggressive interpretation of function type declarations, (declaim (ftype (function ((mod 1024)) (values t &optional)) foo)) (defun foo (x) ... (setf x (bar)) ...) does not guarantee that X is of type (mod 1024) after SETF. Of course, something else might be happening, and it may even be an issue the compiler; it's hard to be more specific without concrete examples. If portability is not too much of a concern, (setf sb-ext:*derive-function-types* t) will record inferred function types in the global environment. This must sometimes be combined with the unportable VALUES declaration to describe return types. > On a related note, if I declare a function's return type in an FTYPE > declaration and bind a variable using the function, as in: > > (declaim (ftype (function (&rest) <type>)) foo) > > (let ((var (foo a b c))) > ...) > > will the compiler infer that VAR has type <type>, or should I declare > that at the top of the LET's body (assuming I want maximal performance)? Yes. Something like (function (&rest t) (values <type> &optional)) is even better: it specifies that a single value is returned. Paul Khuong |
From: Nathan T. <nb...@nb...> - 2013-10-18 22:53:24
|
Paul Khuong <pv...@pv...> writes: > In article <87o...@nb...>, > Nathan Trapuzzano <nb...@nb...> wrote: >> And since function type declarations also declare return >> types, I figured it made more sense to keep the FTYPE declarations and >> remove the TYPE declarations from the DEFUNs. > > There is a difference: FTYPE declarations describe how the function is > called. In theory, this can make it difficult to specialise a function > with FTYPEd information. SBCL and CMUCL are slightly more aggressive > than strictly allowed by the standard and do propagate that information. > So, in practice, for SBCL, the difference is tiny. I'm new to Lisp. What do you mean by "specialise a function"? Are you able to show me in the standard where such propagation is prohibited? >> However, when I ran some tests after removing the declarations, I >> noticed that performance had taken a huge hit. Certain procedures took >> almost twice as long as they had taken before I had removed the type >> declarations. (The optimize setting being (optimize (debug 0) (safety >> 0) speed).) Can anyone here explain why this might be? > > One common issue is with assignment to formal parameters (and with > default values for optional/keyword arguments sometimes). FTYPE > declarations only describe calls to the function. Thus, even with SBCL's > aggressive interpretation of function type declarations, > > (declaim (ftype (function ((mod 1024)) (values t &optional)) foo)) > (defun foo (x) > ... > (setf x (bar)) > ...) > > does not guarantee that X is of type (mod 1024) after SETF. Of course, > something else might be happening, and it may even be an issue the > compiler; it's hard to be more specific without concrete examples. > > If portability is not too much of a concern, > (setf sb-ext:*derive-function-types* t) > will record inferred function types in the global environment. This must > sometimes be combined with the unportable VALUES declaration to describe > return types. In the definitions I have in mind, there are no assignments, so that can't be it. I'm a little confused by your answer. You say above that SBCL and CMUCL do propagate information, but here you say (I think) that that only happens if you set that variable to T. Have I misunderstood you? Also, what do you mean by "unportable VALUES declarations"? >> On a related note, if I declare a function's return type in an FTYPE >> declaration and bind a variable using the function, as in: >> >> (declaim (ftype (function (&rest) <type>)) foo) >> >> (let ((var (foo a b c))) >> ...) >> >> will the compiler infer that VAR has type <type>, or should I declare >> that at the top of the LET's body (assuming I want maximal performance)? > > Yes. Something like (function (&rest t) (values <type> &optional)) is > even better: it specifies that a single value is returned. Good to know, thanks. However, I'm still not really sure what I should do. Should I add those TYPE declarations back in despite the fact that they're already there in the FTYPE declarations? Why else would removing the TYPE declarations cause a performance hit? |