From: David A. V. <ven...@ms...> - 2009-08-07 17:17:13
|
Hi! How exactly does one use (declare (optimize (speed 3) (safety0))), as in the following code example, which is adapted from the last chapter in "Practical Common Lisp"? (defun add (x y) (declare (optimize (speed 3) (safety 0))) (declare (double-float x y)) (the double-float (+ x y))) In Peter Seibel's book, x and y are fixnums, which seems to work ok in SBCL. With double-floats, however, it emits this note: note: doing float to pointer coercion (cost 13) to "<return value>" Not a huge deal for me, but I'm just trying to figure out how all these things work. All the best, David -- David A. Ventimiglia <ven...@ms...> Michigan State University |
From: Tobias C. R. <tc...@fr...> - 2009-08-07 22:05:18
|
"David A. Ventimiglia" <ven...@ms...> writes: > Hi! > > How exactly does one use (declare (optimize (speed 3) (safety0))), as in > the following code example, which is adapted from the last chapter in > "Practical Common Lisp"? > > (defun add (x y) > (declare (optimize (speed 3) (safety 0))) > (declare (double-float x y)) > (the double-float (+ x y))) > > In Peter Seibel's book, x and y are fixnums, which seems to work ok in > SBCL. With double-floats, however, it emits this note: > > note: doing float to pointer coercion (cost 13) to "<return value>" > > Not a huge deal for me, but I'm just trying to figure out how all these > things work. The note means that the return value of ADD will be boxed, i.e. a kind of wrapper around the actual floating point number will be allocated on the heap. If you know Java, it's the same difference between double, the intermediate value, and Double, the object. The reason for boxing is that all values in Lisp pertain their type until and during run-time (for example, so the garbage collector knows how to deal with them.) This is most often achieved by tagging, that is by reserving a few bits of each pointer to be used for type discrimination. That's also the reason why the range of fixnums is smaller than the range of your machine's word size. You cannot shave off bits from double-floats because their length is specified by IEEE to be 64 bits. So you have to go the boxing route where the box contains the type information. (Not that there's any requirement to follow IEEE. I do not know how good or bad an idea it would be to shave off some bits of the mantissa. Perhaps someone else can chime in here?) For performance reason, however, you do not want that additional level of indirection, especially in your inner loops---just like you'd want to avoid Double objects in your inner loops in Java. For intraprocedural code, when it's clear for SBCL that no double float is going to be used across a function's boundary, it's clever enough to avoid the boxing. In practise, you can loose the boundaries by means of inline functions. (I'm not sure how exactly SBCL tells the GC which may come across such values during examination of registers/the stack?) In your example, the float values crosses that boundary, and hence will have to be heap-allocated. In principal, with a global ftype declaration of the function, a compiler could be smart enough to avoid the boxing in many cases, I think, but that's beyond SBCL current optimization capabilities. -T. |
From: Martin C. <cra...@co...> - 2009-08-07 22:59:19
|
David A. Ventimiglia wrote on Fri, Aug 07, 2009 at 10:16:50AM -0700: > Hi! > > How exactly does one use (declare (optimize (speed 3) (safety0))), as in > the following code example, which is adapted from the last chapter in > "Practical Common Lisp"? > > (defun add (x y) > (declare (optimize (speed 3) (safety 0))) > (declare (double-float x y)) > (the double-float (+ x y))) > > In Peter Seibel's book, x and y are fixnums, which seems to work ok in > SBCL. With double-floats, however, it emits this note: > > note: doing float to pointer coercion (cost 13) to "<return value>" Return values and function arguments will have to be type-tagged and in the case of floats that means full boxing. Even in fast mode as this fast-mode module might be called from safe-mode code. The canonical way around this is to pass around arrays or struct instances that have slots of the desired type. Of course that is not worth it if you have to allocated an array or struct instance on every call, you'll have to do reuse. We have seen extremely fast cryptographic code for Common Lisp using a specially allocated array of 32 bit integers as a kind of manually managed heap and pass that around between functions as argument. Integers don't get full boxed like floats, they aren't that bad. But still, they get tag bits and that costs some time, and if you insist on having 32 or 64 bits precisely you are back in boxing mode. %% A quicker fix for this is to inline the function. In your above code that might be all that's needed. Disassemble the calling function to see that you got the desired result. Typically, reading the disassembly can be limited to just looking for calls to memory allocation when your goal is math code of some kind that is not supposed to escape out of just integers or floats. Martin -- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% Martin Cracauer <cra...@co...> http://www.cons.org/cracauer/ FreeBSD - where you want to go, today. http://www.freebsd.org/ |
From: David A. V. <ven...@ms...> - 2009-08-08 17:08:37
|
Thanks for the detailed explanations. So then, should something like this be sufficient, provided inlining is supported? (declaim (inline add)) (defun add (x y) (declare (optimize (speed 3) (safety 0))) (declare (double-float x y)) (the double-float (+ x y))) On Fri, 2009-08-07 at 18:59 -0400, Martin Cracauer wrote: > David A. Ventimiglia wrote on Fri, Aug 07, 2009 at 10:16:50AM -0700: > > Hi! > > > > How exactly does one use (declare (optimize (speed 3) (safety0))), as in > > the following code example, which is adapted from the last chapter in > > "Practical Common Lisp"? > > > > (defun add (x y) > > (declare (optimize (speed 3) (safety 0))) > > (declare (double-float x y)) > > (the double-float (+ x y))) > > > > In Peter Seibel's book, x and y are fixnums, which seems to work ok in > > SBCL. With double-floats, however, it emits this note: > > > > note: doing float to pointer coercion (cost 13) to "<return value>" > > Return values and function arguments will have to be type-tagged and > in the case of floats that means full boxing. Even in fast mode as > this fast-mode module might be called from safe-mode code. > > The canonical way around this is to pass around arrays or struct > instances that have slots of the desired type. Of course that is not > worth it if you have to allocated an array or struct instance on every > call, you'll have to do reuse. > > We have seen extremely fast cryptographic code for Common Lisp using a > specially allocated array of 32 bit integers as a kind of manually > managed heap and pass that around between functions as argument. > > Integers don't get full boxed like floats, they aren't that bad. But > still, they get tag bits and that costs some time, and if you insist > on having 32 or 64 bits precisely you are back in boxing mode. > > %% > > A quicker fix for this is to inline the function. In your above code > that might be all that's needed. Disassemble the calling function to > see that you got the desired result. Typically, reading the > disassembly can be limited to just looking for calls to memory > allocation when your goal is math code of some kind that is not > supposed to escape out of just integers or floats. > > Martin -- David A. Ventimiglia <ven...@ms...> Michigan State University |
From: Thomas F. B. <tbu...@gm...> - 2009-08-08 17:45:06
|
2009/8/8 David A. Ventimiglia <ven...@ms...>: > Thanks for the detailed explanations. So then, should something like > this be sufficient, provided inlining is supported? You're talking about SBCL, so yes, inlining is supported. > (declaim (inline add)) This should suffice. > (defun add (x y) > (declare (optimize (speed 3) (safety 0))) > (declare (double-float x y)) > (the double-float (+ x y))) Two things here. First, you probably don't want to use (safety 0). It has its uses, mostly when you want to lie to the compiler and you know that it's okay to do so. Where you might get efficiency notes at (safety 1), you'll likely get incorrect runtime behavior at (safety 0). Better to figure out what the notes are trying to tell you, and to ask here if you can't. Second, you want to be careful with using (the ...). It will turn into a run-time assertion if the compiler can't figure out at runtime that what you're asserting is true. In this case, it's a no-op, since adding two double-floats results in a double-float. What you probably want to write here is this: (declaim (inline add)) (defun add (x y) (declare (optimize (speed 3) (safety 1)) (double-float x y)) (+ x y)) Then handle notes at the usage sites as they come up. Read the notes, try to understand what they say, look at the disassembly and ask here as needed. Try to insert the minimum of assertions necessary and you'll get the hang of it. One final tip, if you're going to be writing code like this. SBCL no longer has a block-compiler, but you can get some similar benefits if you use the following idiom: (defun static-ish-load (file) (let ((sb-ext:*derive-function-types* t)) (compile-file file) (load (compile-file file)))) |
From: David A. V. <ven...@ms...> - 2009-08-10 16:33:31
|
Thanks, again. I've another question about these optimization strategies, then. What's your (or others') advice on attempting these optimizations when working with interdependent functions. What I mean is this. I have this function, which calculates a Gaussian conditional probability density function of l given m, and uses the GNU Scientific Library via GSLL (http://common-lisp.net/project/gsll/) to do it. It's one of the factors in an integrand that gets evaluated many, many times. (declaim (inline P-l-given-m)) (defun P-l-given-m (l m z omega-m-0 omega-l-0 w log-Ax beta sig-l) (declare (optimize (speed 3) (safety 1)) (double-float l m z omega-m-0 omega-l-0 w log-Ax beta sig-l)) (let* ((l-expected (l-of-m m z omega-m-0 omega-l-0 w log-Ax beta)) (del-l (- l l-expected))) (gaussian-pdf (coerce del-l 'double-float) (coerce sig-l 'double-float)))) I get compiler notes for the 6th line, with this form (del-l (- l l-expected) and on the 8th line, with this form (coerce del-l 'double-float) essentially because it is not known to the compiler that l-expected is a double-float. So, what are ways to deal with this? Do I go to the function l-of-m and declare its parameters and return type? I can do that, but that function itself calls other functions, so won't I have to chase them down as well? Is it possible to pick out one function, which may not be a leaf function in the tree of calls, and insert declarations into it in isolation, or do you have to visit every function in a chain of calls, declaring types for each in turn, and keep doing that until you've mopped up all the compiler notes? This is not exactly an SBCL question, so I can rotate this over to, say, comp.lang.lisp if that's preferred. Thanks. All the best, David. On Sat, 2009-08-08 at 19:44 +0200, Thomas F. Burdick wrote: > 2009/8/8 David A. Ventimiglia <ven...@ms...>: > > Thanks for the detailed explanations. So then, should something like > > this be sufficient, provided inlining is supported? > > You're talking about SBCL, so yes, inlining is supported. > > > (declaim (inline add)) > > This should suffice. > > > (defun add (x y) > > (declare (optimize (speed 3) (safety 0))) > > (declare (double-float x y)) > > (the double-float (+ x y))) > > Two things here. First, you probably don't want to use (safety 0). It > has its uses, mostly when you want to lie to the compiler and you know > that it's okay to do so. Where you might get efficiency notes at > (safety 1), you'll likely get incorrect runtime behavior at (safety > 0). Better to figure out what the notes are trying to tell you, and to > ask here if you can't. > > Second, you want to be careful with using (the ...). It will turn into > a run-time assertion if the compiler can't figure out at runtime that > what you're asserting is true. In this case, it's a no-op, since > adding two double-floats results in a double-float. > > What you probably want to write here is this: > > (declaim (inline add)) > (defun add (x y) > (declare (optimize (speed 3) (safety 1)) > (double-float x y)) > (+ x y)) > > Then handle notes at the usage sites as they come up. Read the notes, > try to understand what they say, look at the disassembly and ask here > as needed. Try to insert the minimum of assertions necessary and > you'll get the hang of it. > > One final tip, if you're going to be writing code like this. SBCL no > longer has a block-compiler, but you can get some similar benefits if > you use the following idiom: > > (defun static-ish-load (file) > (let ((sb-ext:*derive-function-types* t)) > (compile-file file) > (load (compile-file file)))) > -- David A. Ventimiglia <ven...@ms...> Michigan State University |
From: Leslie P. P. <sk...@vi...> - 2009-08-10 16:43:01
|
David A. Ventimiglia wrote: > So, what are ways to deal with this? Do I go to the function l-of-m and > declare its parameters and return type? I can do that, but that > function itself calls other functions, so won't I have to chase them > down as well? Is it possible to pick out one function, which may not be > a leaf function in the tree of calls, and insert declarations into it in > isolation, or do you have to visit every function in a chain of calls, > declaring types for each in turn, and keep doing that until you've > mopped up all the compiler notes? Have you tried to DECLARE l-expected to be a double-float after its LET line? Leslie -- http://www.linkedin.com/in/polzer |
From: David A. V. <ven...@ms...> - 2009-08-10 17:49:24
|
I did, after Martin made that recommendation in an earlier post. Thanks to both of you. Best, David On Mon, 2009-08-10 at 18:42 +0200, Leslie P. Polzer wrote: > David A. Ventimiglia wrote: > > > So, what are ways to deal with this? Do I go to the function l-of-m and > > declare its parameters and return type? I can do that, but that > > function itself calls other functions, so won't I have to chase them > > down as well? Is it possible to pick out one function, which may not be > > a leaf function in the tree of calls, and insert declarations into it in > > isolation, or do you have to visit every function in a chain of calls, > > declaring types for each in turn, and keep doing that until you've > > mopped up all the compiler notes? > > Have you tried to DECLARE l-expected to be a double-float after its LET > line? > > Leslie > -- David A. Ventimiglia <ven...@ms...> Michigan State University |
From: Martin C. <cra...@co...> - 2009-08-10 16:56:25
|
David A. Ventimiglia wrote on Mon, Aug 10, 2009 at 09:33:20AM -0700: > Thanks, again. I've another question about these optimization > strategies, then. What's your (or others') advice on attempting these > optimizations when working with interdependent functions. What I mean > is this. I have this function, which calculates a Gaussian conditional > probability density function of l given m, and uses the GNU Scientific > Library via GSLL (http://common-lisp.net/project/gsll/) to do it. It's > one of the factors in an integrand that gets evaluated many, many times. > > (declaim (inline P-l-given-m)) > (defun P-l-given-m (l m z omega-m-0 omega-l-0 w log-Ax beta sig-l) > (declare (optimize (speed 3) (safety 1)) > (double-float l m z omega-m-0 omega-l-0 w log-Ax beta sig-l)) > (let* ((l-expected (l-of-m m z omega-m-0 omega-l-0 w log-Ax beta)) > (del-l (- l l-expected))) > (gaussian-pdf > (coerce del-l 'double-float) > (coerce sig-l 'double-float)))) I'm afraid this general style of coding for doing floating point work gets you nowhere in Common Lisp, any existing implementation. Since we are not allowed to cut a couple of bits off floats (like we do for integers) any kind of cross-function boundary will kill you sooner or later. Inlining helps but obviously you will either have to put a stop on inlining and hence suffer horrendously from boxing, or you will drive your whole code package into a hell of cache misses from too much inlining. If you really need this to run fast you will have to operate exclusively on struct members or on floats in arrays. > I get compiler notes for the 6th line, with this form > > (del-l (- l l-expected) > > and on the 8th line, with this form > > (coerce del-l 'double-float) > > essentially because it is not known to the compiler that l-expected is a > double-float. > > So, what are ways to deal with this? A declaration inside the let* should get that solved. > Do I go to the function l-of-m and > declare its parameters and return type? That helps only a little bit. Declarations on the arguments and return types of functions (via ftype declarations) skip a couple of type checks, but those are very cheap on a modern CPU. The boxing, if not inline, is orders of magnitude more expensive. > I can do that, but that > function itself calls other functions, so won't I have to chase them > down as well? Is it possible to pick out one function, which may not be > a leaf function in the tree of calls, and insert declarations into it in > isolation, or do you have to visit every function in a chain of calls, > declaring types for each in turn, and keep doing that until you've > mopped up all the compiler notes? This isn't a good strategy I'm afraid. The absence of compiler notes doesn't mean the code runs fast, as in near raw machine speed. What you need is constant space, compact data, with no run-time type conversions to and from raw machine formats. The type checks aren't you probably problem. Martin -- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% Martin Cracauer <cra...@co...> http://www.cons.org/cracauer/ FreeBSD - where you want to go, today. http://www.freebsd.org/ |
From: David A. V. <ven...@ms...> - 2009-08-10 17:35:16
|
I see. And structs are one way of (possibly) achieving constant-space, compact data? Maybe that's within reach for me, given that, what I have is a tree of function calls, most of which use mostly the same parameters. Most of them need some or all members of a set of model parameters, theta_1 through theta_N, which are constant throughout the chain of calls. A few of the functions are integrands, so they take dummy variables which obviously are not constant, in addition to the constant model parameters. Finally, there are a few functions (typically very small, utility, and often simply converting functions) that don't take any of the model parameters. I could start by stuffing my model parameters, theta_1 through theta_N, as double-floats into a struct, making an instance of that struct at the root of the tree of calls, then propagating that same instance (without allocating copies) throughout the tree. Is that a reasonable way to start? Then what do I do about return values? Typically, as in the functions I'm most concerned about, which are in the integrands, a function will take some model parameters plus some dummy parameters, do some math, and return a number. I guess I want that return value to be unboxed, just as I want my parameters to be unboxed. Could I reserve a slot (or slots) in my struct for return values? All the best, David On Mon, 2009-08-10 at 12:56 -0400, Martin Cracauer wrote: > David A. Ventimiglia wrote on Mon, Aug 10, 2009 at 09:33:20AM -0700: > > Thanks, again. I've another question about these optimization > > strategies, then. What's your (or others') advice on attempting these > > optimizations when working with interdependent functions. What I mean > > is this. I have this function, which calculates a Gaussian conditional > > probability density function of l given m, and uses the GNU Scientific > > Library via GSLL (http://common-lisp.net/project/gsll/) to do it. It's > > one of the factors in an integrand that gets evaluated many, many times. > > > > (declaim (inline P-l-given-m)) > > (defun P-l-given-m (l m z omega-m-0 omega-l-0 w log-Ax beta sig-l) > > (declare (optimize (speed 3) (safety 1)) > > (double-float l m z omega-m-0 omega-l-0 w log-Ax beta sig-l)) > > (let* ((l-expected (l-of-m m z omega-m-0 omega-l-0 w log-Ax beta)) > > (del-l (- l l-expected))) > > (gaussian-pdf > > (coerce del-l 'double-float) > > (coerce sig-l 'double-float)))) > > I'm afraid this general style of coding for doing floating point work > gets you nowhere in Common Lisp, any existing implementation. Since > we are not allowed to cut a couple of bits off floats (like we do for > integers) any kind of cross-function boundary will kill you sooner or > later. Inlining helps but obviously you will either have to put a > stop on inlining and hence suffer horrendously from boxing, or you > will drive your whole code package into a hell of cache misses from > too much inlining. > > If you really need this to run fast you will have to operate > exclusively on struct members or on floats in arrays. > > > I get compiler notes for the 6th line, with this form > > > > (del-l (- l l-expected) > > > > and on the 8th line, with this form > > > > (coerce del-l 'double-float) > > > > essentially because it is not known to the compiler that l-expected is a > > double-float. > > > > So, what are ways to deal with this? > > A declaration inside the let* should get that solved. > > > Do I go to the function l-of-m and > > declare its parameters and return type? > > That helps only a little bit. Declarations on the arguments and > return types of functions (via ftype declarations) skip a couple of > type checks, but those are very cheap on a modern CPU. The boxing, if > not inline, is orders of magnitude more expensive. > > > I can do that, but that > > function itself calls other functions, so won't I have to chase them > > down as well? Is it possible to pick out one function, which may not be > > a leaf function in the tree of calls, and insert declarations into it in > > isolation, or do you have to visit every function in a chain of calls, > > declaring types for each in turn, and keep doing that until you've > > mopped up all the compiler notes? > > This isn't a good strategy I'm afraid. The absence of compiler notes > doesn't mean the code runs fast, as in near raw machine speed. > > What you need is constant space, compact data, with no run-time > type conversions to and from raw machine formats. The type checks > aren't you probably problem. > > Martin -- David A. Ventimiglia <ven...@ms...> Michigan State University |
From: David A. V. <ven...@ms...> - 2009-08-10 22:12:16
|
Actually, maybe I can pose my question in a more general way, without distracting references to my particular problem and the code that addresses it. How does one achieve high performance for numerically-intensive applications in SBCL, when the data is primarily floating-point numbers, when you want to preserve a fairly high level of functional decomposition, and when (let's assume this is true) you've exhausted all of the algorithmic optimizations available? I.e., this is the general case in which functions take some floating-point numbers, do some math, and/or call other functions. It sounds to me from the discussion so far that--without knowing anything else about the problem--a good guess as to where performance penalties are being paid is in the boxing/unboxing of floating-point arguments and return values, and if that's true, passing and returning floats as elements of arrays or members of structs, is preferable to passing and returning the floats directly. Am I even close to being right? :) All the best, David On Mon, 2009-08-10 at 10:35 -0700, David A. Ventimiglia wrote: > I see. And structs are one way of (possibly) achieving constant-space, > compact data? Maybe that's within reach for me, given that, what I have > is a tree of function calls, most of which use mostly the same > parameters. Most of them need some or all members of a set of model > parameters, theta_1 through theta_N, which are constant throughout the > chain of calls. A few of the functions are integrands, so they take > dummy variables which obviously are not constant, in addition to the > constant model parameters. Finally, there are a few functions > (typically very small, utility, and often simply converting functions) > that don't take any of the model parameters. > > I could start by stuffing my model parameters, theta_1 through theta_N, > as double-floats into a struct, making an instance of that struct at the > root of the tree of calls, then propagating that same instance (without > allocating copies) throughout the tree. Is that a reasonable way to > start? > > Then what do I do about return values? Typically, as in the functions > I'm most concerned about, which are in the integrands, a function will > take some model parameters plus some dummy parameters, do some math, and > return a number. I guess I want that return value to be unboxed, just > as I want my parameters to be unboxed. Could I reserve a slot (or > slots) in my struct for return values? > > All the best, > David > > > On Mon, 2009-08-10 at 12:56 -0400, Martin Cracauer wrote: > > David A. Ventimiglia wrote on Mon, Aug 10, 2009 at 09:33:20AM -0700: > > > Thanks, again. I've another question about these optimization > > > strategies, then. What's your (or others') advice on attempting these > > > optimizations when working with interdependent functions. What I mean > > > is this. I have this function, which calculates a Gaussian conditional > > > probability density function of l given m, and uses the GNU Scientific > > > Library via GSLL (http://common-lisp.net/project/gsll/) to do it. It's > > > one of the factors in an integrand that gets evaluated many, many times. > > > > > > (declaim (inline P-l-given-m)) > > > (defun P-l-given-m (l m z omega-m-0 omega-l-0 w log-Ax beta sig-l) > > > (declare (optimize (speed 3) (safety 1)) > > > (double-float l m z omega-m-0 omega-l-0 w log-Ax beta sig-l)) > > > (let* ((l-expected (l-of-m m z omega-m-0 omega-l-0 w log-Ax beta)) > > > (del-l (- l l-expected))) > > > (gaussian-pdf > > > (coerce del-l 'double-float) > > > (coerce sig-l 'double-float)))) > > > > I'm afraid this general style of coding for doing floating point work > > gets you nowhere in Common Lisp, any existing implementation. Since > > we are not allowed to cut a couple of bits off floats (like we do for > > integers) any kind of cross-function boundary will kill you sooner or > > later. Inlining helps but obviously you will either have to put a > > stop on inlining and hence suffer horrendously from boxing, or you > > will drive your whole code package into a hell of cache misses from > > too much inlining. > > > > If you really need this to run fast you will have to operate > > exclusively on struct members or on floats in arrays. > > > > > I get compiler notes for the 6th line, with this form > > > > > > (del-l (- l l-expected) > > > > > > and on the 8th line, with this form > > > > > > (coerce del-l 'double-float) > > > > > > essentially because it is not known to the compiler that l-expected is a > > > double-float. > > > > > > So, what are ways to deal with this? > > > > A declaration inside the let* should get that solved. > > > > > Do I go to the function l-of-m and > > > declare its parameters and return type? > > > > That helps only a little bit. Declarations on the arguments and > > return types of functions (via ftype declarations) skip a couple of > > type checks, but those are very cheap on a modern CPU. The boxing, if > > not inline, is orders of magnitude more expensive. > > > > > I can do that, but that > > > function itself calls other functions, so won't I have to chase them > > > down as well? Is it possible to pick out one function, which may not be > > > a leaf function in the tree of calls, and insert declarations into it in > > > isolation, or do you have to visit every function in a chain of calls, > > > declaring types for each in turn, and keep doing that until you've > > > mopped up all the compiler notes? > > > > This isn't a good strategy I'm afraid. The absence of compiler notes > > doesn't mean the code runs fast, as in near raw machine speed. > > > > What you need is constant space, compact data, with no run-time > > type conversions to and from raw machine formats. The type checks > > aren't you probably problem. > > > > Martin -- David A. Ventimiglia <ven...@ms...> Michigan State University |
From: Martin C. <cra...@co...> - 2009-08-10 22:58:52
|
David A. Ventimiglia wrote on Mon, Aug 10, 2009 at 10:35:05AM -0700: > I see. And structs are one way of (possibly) achieving constant-space, > compact data? Maybe that's within reach for me, given that, what I have > is a tree of function calls, most of which use mostly the same > parameters. Most of them need some or all members of a set of model > parameters, theta_1 through theta_N, which are constant throughout the > chain of calls. A few of the functions are integrands, so they take > dummy variables which obviously are not constant, in addition to the > constant model parameters. Finally, there are a few functions > (typically very small, utility, and often simply converting functions) > that don't take any of the model parameters. > > I could start by stuffing my model parameters, theta_1 through theta_N, > as double-floats into a struct, making an instance of that struct at the > root of the tree of calls, then propagating that same instance (without > allocating copies) throughout the tree. Is that a reasonable way to > start? If you can do that, that's great. The whole issue isn't pretty. Most people cannot preallocate structs and pass them around in the right numbers without huge effort, introducing bugs from failed reuse in the process. Most floating point number crunching code is working on large amounts of numbers, so it is natural to just have them in arrays and then there is no problem. But for finer code that actually munches on single numbers a lot it is a pain. In general, if you drive this project a few years you will find that you wrong a function-writing macro that defines stuff than looks like functions in source code but bends and rewrites all the parameter and return value to happen in some overengineered "thing" that nobody understands 6 months in the future. > Then what do I do about return values? Typically, as in the functions > I'm most concerned about, which are in the integrands, a function will > take some model parameters plus some dummy parameters, do some math, and > return a number. I guess I want that return value to be unboxed, just > as I want my parameters to be unboxed. Could I reserve a slot (or > slots) in my struct for return values? Exactly. Stuff them somewhere into the composite input parameters. Use the return value for e.g. error signaling (NIL/T). Martin -- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% Martin Cracauer <cra...@co...> http://www.cons.org/cracauer/ FreeBSD - where you want to go, today. http://www.freebsd.org/ |