I thought I'd report back briefly on some results.
First, I spent some time attempting to implement "perform with session state". This is the idea that when working with ASDF, it would be useful to be able to invoke or create specific behavior for my use case and also to be able to capture work session state without relying on globals. This was partially successful, but I broke something and ran out of patience. ASDF is some wonderful code, but takes a bit to grok it all.
So I took the crude approach of just installing a global and using it to accumulate loaded files from within a perform. I created an .asd file which defined the dependencies I wanted the image to have. The loaded it with load-system. Then at the end of load-system I inserted code to save a structure containing a list of the *defined-systems* as well as a list of the loaded files (in order of loading).
The next step was to create a little loader script which loads all of the files, and then populates *modules* via satisfies. There were a couple of glitches. For one, a couple of systems had hard coded asdf dependencies. After removing them, they compiled fine. Then another system required asdf-system-connections. I needed to remove that out of the file load list because it isn't necessary after things are loaded.
Finally the load list works, the image saves and starts just fine. I can use regular ol' REQUIRE to provide a sanity check, although I realize this may not be the best approach, at least it works without extra code.
This saved about 10% on the image size compared to asdf-loading the same systems, and cleared out some unnecessary dependencies. It will take a while to work out the best "batteries included" image. I can always load ASDF for a given project if it needs to incorporate other systems, smaller projects can just load themselves.
Of course for development I'll still be using ASDF a hundred times a day!
Erik.
On Mon, Apr 23, 2012 at 2:10 PM, Robert Goldman rpgoldman@sift.info wrote:
On 4/23/12 Apr 23 -3:52 PM, Erik Pearson wrote:
Hi Robert, What I was thinking is that OPERATE assembles the plan via TRAVERSE, then executes it via PERFORM-PLAN, which calls PERFORM to translate the abstraction of operation + component into an action with side effect. It seems to me that it is each PERFORM (or, rather, the ones that we are interested in) that needs to be captured, since it is only in the body of the PERFORM method that the abstractions are made concrete in into lisp forms which carry out the desired actions.
What I am referring to as "recording" is to append the lisp forms produced by each PERFORM into a list or other structure. At present this structure would need to be a global, but ideally it would be a slot in a session object that threads through each PERFORM. It is because the PERFORMS are carried out serially by PERFORM-PLAN according to the plan assembled by TRAVERSE that they can be "played back" to recreate the actions of this asdf session. Now if there were parallel executions of PERFORMs by PERFORM-PLAN, that would be different...
I hope that makes sense.
I suppose, but I don't know how you break into what PERFORM does.
A quick and dirty way, as I described above, does require breaking into PERFORM. But I think you could either create an alternate method and inject that at around the PERFORM-PLAN level, or use a SESSION object as an additional specializer argument to open up other possible PERFORM implementations.
I was thinking you would precompute and cache the plan, and then invoke PERFORM-PLAN on that.
Or maybe another method altogether to take the place of perform, like SIMULATE-PLAN or RECORD-PLAN, or ASSEMBLE-PLAN.
That assumes that the call to TRAVERSE is actually expensive enough that this is worth the trouble. I think you could confirm or disconfirm that claim quickly with some timing experiments.
Yes, I think I did that -- but with a different concern. When I was concerned about ASDF mucking around inspecting files when I was doing a REQUIRE, I added a feature to cause a fully loaded module to be flagged as loaded via mark-operation-done, so that do-traverse would stop traversing at such a module. It was about 2.5 seconds to traverse normally, and about 0.5 with this optimization for a require after the first one (of course for a deployed system not development.)
In any case load time is not my only interest. It is also a desire to be able to use ASDF more effectively for more use cases.
Erik.
cheers,
r
Your results are very interesting, but it would still be more interesting to see some analysis of the runtimes.
Why does it save to *not* use ASDF for loading? Is it
* the preparation of the system (handling the configuration files and locating all the .asd's)?
* the call to TRAVERSE to build the load plan? or
* the checking of file write dates inside PERFORM-PLAN?
Do you have any information about this?
cheers, r
Dear Erik,
I thought I'd report back briefly on some results.
Thanks for trying. Can you share your code?
First, I spent some time attempting to implement "perform with session state". This is the idea that when working with ASDF, it would be useful to be able to invoke or create specific behavior for my use case and also to be able to capture work session state without relying on globals. This was partially successful, but I broke something and ran out of patience. ASDF is some wonderful code, but takes a bit to grok it all.
In the past, I've slightly modified ASDF to make it easier for POIU to do its job. If there's some interface I can expose so your hack can be an extension of ASDF rather than a wholesale replacement of its functionality, I'll be glad.
So I took the crude approach of just installing a global and using it to accumulate loaded files from within a perform. I created an .asd file which defined the dependencies I wanted the image to have. The loaded it with load-system. Then at the end of load-system I inserted code to save a structure containing a list of the *defined-systems* as well as a list of the loaded files (in order of loading).
NB: POIU also has a mode to record and replay a list of accumulated files.
Am I correct that these are the things you're trying to do? (1) load all the dependencies (2) record what are the things loaded so far (3) load your application (4) when you refresh your application, skip the recorded dependencies.
If so, then I propose you use the functionality I added to 2.20.21 for the measly cost of 12 lines of code, registered-systems and :force-not:
(asdf:load-systems '(dependency-a dependency-b)) ;(1) (defparameter *recorded-dependencies* (asdf:registered-systems)) ;(2) (asdf:load-system :my-application) ;(3) (asdf:load-system :my-application :force-not *recorded-dependencies*) ;(4)
If you want more fine-grained control, you could provide an override defmethod operation-done-p :around
If you like my :force-not feature, I'd appreciate you write a test case in the asdf test suite.
It was about 2.5 seconds to traverse normally, and about 0.5 with this optimization for a require after the first one (of course for a deployed system not development.)
Can you confirm it works well with my proposed solution?
In any case load time is not my only interest. It is also a desire to be able to use ASDF more effectively for more use cases.
Same here.
—♯ƒ • François-René ÐVB Rideau •Reflection&Cybernethics• http://fare.tunes.org It is a miracle that curiosity survives formal education. — Albert Einstein
Hi Faré,
On Tue, Apr 24, 2012 at 8:02 AM, Faré fahree@gmail.com wrote:
Dear Erik,
I thought I'd report back briefly on some results.
Thanks for trying. Can you share your code?
I'd be glad to -- perhaps after a bit more tinkering so that at least the changes are "switchable" via a global flag.
First, I spent some time attempting to implement "perform with session state". This is the idea that when working with ASDF, it would be useful to be able to invoke or create specific behavior for my use case and also to be able to capture work session state without relying on globals. This was partially successful, but I broke something and ran out of patience. ASDF is some wonderful code, but takes a bit to grok it all.
In the past, I've slightly modified ASDF to make it easier for POIU to do its job. If there's some interface I can expose so your hack can be an extension of ASDF rather than a wholesale replacement of its functionality, I'll be glad.
So I took the crude approach of just installing a global and using it to accumulate loaded files from within a perform. I created an .asd file which defined the dependencies I wanted the image to have. The loaded it with load-system. Then at the end of load-system I inserted code to save a structure containing a list of the *defined-systems* as well as a list of the loaded files (in order of loading).
NB: POIU also has a mode to record and replay a list of accumulated files.
Am I correct that these are the things you're trying to do? (1) load all the dependencies
yes
(2) record what are the things loaded so far
yes
(3) load your application (4) when you refresh your application, skip the recorded dependencies.
Basically, yes. I would state, though, that I want the ASDF to work like the primary contract of REQUIRE. That is, this clause from CLHS:
require tests for the presence of the module-name in the list held by *modules*. If it is present, require immediately returns.
I think that ASDF's default mode of grokking the filesystem for changes is what I want in a development environment.
However, it is not what I want for systems that I'm just depending on and (not necessarily) modifying. It should compile those if needed, and load those just one time.
And it it not what I want in a system that is deployed, because the source code for loaded modules will never change, and if I need to work some patches in or load some runtime functions, that will be under application control
I realize that I'm expecting ASDF to magically behave differently when developing code, to discover changed files and recompile and reload them. Thus the need for a global flag to tell ASDF its operating mode.
One culprit, it seems, is REQUIRE. Otherwise, ASDF wouldn't do anything in a normal system without ASDF specific calls. REQUIRE is not used all that much in Lisp code, not nearly as much as an import in other languages which depend on it. And I would partly blame the big confusion between PACKAGE, DEFSYSTEM, and REQUIRE -- it is kind of a mess. Still, REQUIRE does pop up in library code, and it is a very easy to use and easy to understand way of both expressing a dependency and possibly loading your toplevel system when developing, deploying, or running code ad-hoc. And it does sorta map from modules to systems, albeit with no facility for dependencies.
Another goal, though, is to reduce the noise in the generated image. If the image is created after ASDF has been doing its work, it is not necessarily suitable for deployment. Maybe the target application needs ASDF, maybe it doesn't. Plus I just get nervous with an image that has just been worked on (Lisp has enough moving parts as it is.) Having the load plan, or rather the plan implementation code, recorded, saved, and loaded into a fresh image would allow the omission of ASDF, its dependencies, or any other system-building code and data structures. The target image might not even have a compiler or code loading functionality. On the other hand, it might be desirable to include ASDF and initialize the ASDF system database with some or all of the loaded systems marked as not-reloadable.
If so, then I propose you use the functionality I added to 2.20.21 for the measly cost of 12 lines of code, registered-systems and :force-not:
Aww, you shouldn't have :)
(asdf:load-systems '(dependency-a dependency-b)) ;(1) (defparameter *recorded-dependencies* (asdf:registered-systems)) ;(2) (asdf:load-system :my-application) ;(3) (asdf:load-system :my-application :force-not *recorded-dependencies*) ;(4)
If you want more fine-grained control, you could provide an override defmethod operation-done-p :around
If you like my :force-not feature, I'd appreciate you write a test case in the asdf test suite.
It was about 2.5 seconds to traverse normally, and about 0.5 with this optimization for a require after the first one (of course for a deployed system not development.)
Can you confirm it works well with my proposed solution?
In any case load time is not my only interest. It is also a desire to be able to use ASDF more effectively for more use cases.
Same here.
I really appreciate the responses and the new code to play with. That really is exceptional. I'll send some feedback later, hopefully today, but I have a message queue processing system to build and a web site to get up :(
Erik.
—♯ƒ • François-René ÐVB Rideau •Reflection&Cybernethics• http://fare.tunes.org It is a miracle that curiosity survives formal education. — Albert Einstein
Basically, yes. I would state, though, that I want the ASDF to work like the primary contract of REQUIRE. That is, this clause from CLHS:
require tests for the presence of the module-name in the list held by *modules*. If it is present, require immediately returns.
I think that ASDF's default mode of grokking the filesystem for changes is what I want in a development environment.
Is this what you want? Should I commit that (exporting the symbols) as asdf 2.20.22?
(in-package :asdf) (defun component-loaded-p (c) (and (gethash 'load-op (component-operation-times (find-component c nil))) t)) (defun loaded-systems () (remove-if-not 'component-loaded-p (registered-systems))) (defun require-system (s) (load-system s :force-not (loaded-systems)))
I really appreciate the responses and the new code to play with. That really is exceptional. I'll send some feedback later, hopefully today, but I have a message queue processing system to build and a web site to get up :(
Have fun!
—♯ƒ • François-René ÐVB Rideau •Reflection&Cybernethics• http://fare.tunes.org You can only find happiness by striving towards something else.
Magically minimal. It looks great!
On Tue, Apr 24, 2012 at 9:36 AM, Faré fahree@gmail.com wrote:
Basically, yes. I would state, though, that I want the ASDF to work like the primary contract of REQUIRE. That is, this clause from CLHS:
require tests for the presence of the module-name in the list held by *modules*. If it is present, require immediately returns.
I think that ASDF's default mode of grokking the filesystem for changes is what I want in a development environment.
Is this what you want? Should I commit that (exporting the symbols) as asdf 2.20.22?
(in-package :asdf) (defun component-loaded-p (c) (and (gethash 'load-op (component-operation-times (find-component c nil))) t)) (defun loaded-systems () (remove-if-not 'component-loaded-p (registered-systems))) (defun require-system (s) (load-system s :force-not (loaded-systems)))
I really appreciate the responses and the new code to play with. That really is exceptional. I'll send some feedback later, hopefully today, but I have a message queue processing system to build and a web site to get up :(
Have fun!
—♯ƒ • François-René ÐVB Rideau •Reflection&Cybernethics• http://fare.tunes.org You can only find happiness by striving towards something else.
Initial results for require-system are improved over load system, for sure:
To successfully find a system:
asdf:require-system = 0.2 seconds asdf:load-system = 2.2 seconds require = 0.001 seconds member of *modules* = 0.00005 seconds
To fail to find a system ("notasystem")
asdf:require-system = 0.21 sec asdf:load-system = 0.007 sec require = 0.1 sec (member .. *modules*) = 0.00006 sec
Since failing to find a system results in an exception for the first three cases, time is probably less important a measurement in this case.
? (time (asdf:require-system :messageq)) (ASDF:REQUIRE-SYSTEM :MESSAGEQ) took 218,210 microseconds (0.218210 seconds) to run. 4,523 microseconds (0.004523 seconds, 2.07%) of which was spent in GC. During that period, and with 4 available CPU cores, 179,988 microseconds (0.179988 seconds) were spent in user mode 36,664 microseconds (0.036664 seconds) were spent in system mode 2,175,856 bytes of memory allocated. T ? (time (asdf:load-system :messageq)) (ASDF:LOAD-SYSTEM :MESSAGEQ) took 2,198,531 microseconds (2.198531 seconds) to run. 49,693 microseconds (0.049693 seconds, 2.26%) of which was spent in GC. During that period, and with 4 available CPU cores, 1,839,880 microseconds (1.839880 seconds) were spent in user mode 353,310 microseconds (0.353310 seconds) were spent in system mode 21,279,072 bytes of memory allocated. T
But then again, for perspective ("LISTS" is the last module in the list.)
? (time (require "LISTS")) (REQUIRE "LISTS") took 138 microseconds (0.000138 seconds) to run. During that period, and with 4 available CPU cores, 0 microseconds (0.000000 seconds) were spent in user mode 0 microseconds (0.000000 seconds) were spent in system mode 1,296 bytes of memory allocated. "LISTS" NIL
? (time (member "LISTS" *modules* :test 'equal)) (MEMBER "LISTS" *MODULES* :TEST 'EQUAL) took 51 microseconds (0.000051 seconds) to run. During that period, and with 4 available CPU cores, 0 microseconds (0.000000 seconds) were spent in user mode 0 microseconds (0.000000 seconds) were spent in system mode ("LISTS") ?
And one more, time to handle missing module:
(time (ignore-errors (require "list"))) (IGNORE-ERRORS (REQUIRE "list")) took 100,039 microseconds (0.100039 seconds) to run. 5,587 microseconds (0.005587 seconds, 5.58%) of which was spent in GC. During that period, and with 4 available CPU cores, 73,329 microseconds (0.073329 seconds) were spent in user mode 26,665 microseconds (0.026665 seconds) were spent in system mode 1,318,768 bytes of memory allocated. 2 minor page faults, 0 major page faults, 0 swaps.
? (time (ignore-errors (asdf:load-system "notamodule"))) (IGNORE-ERRORS (ASDF:LOAD-SYSTEM "notamodule")) took 6,925 microseconds (0.006925 seconds) to run. During that period, and with 4 available CPU cores, 6,666 microseconds (0.006666 seconds) were spent in user mode 0 microseconds (0.000000 seconds) were spent in system mode 52,880 bytes of memory allocated. NIL Component "notamodule" not found
? (time (ignore-errors (asdf:require-system "notamodule"))) (IGNORE-ERRORS (ASDF:REQUIRE-SYSTEM "notamodule")) took 210,796 microseconds (0.210796 seconds) to run. 4,817 microseconds (0.004817 seconds, 2.29%) of which was spent in GC. During that period, and with 4 available CPU cores, 179,988 microseconds (0.179988 seconds) were spent in user mode 29,998 microseconds (0.029998 seconds) were spent in system mode 2,210,896 bytes of memory allocated. NIL Component "notamodule" not found ?
? (time (member "notamodule" *modules* :test 'equal)) (MEMBER "notamodule" *MODULES* :TEST 'EQUAL) took 61 microseconds (0.000061 seconds) to run. During that period, and with 4 available CPU cores, 0 microseconds (0.000000 seconds) were spent in user mode 0 microseconds (0.000000 seconds) were spent in system mode NIL
NIL Erik.
On Tue, Apr 24, 2012 at 11:26 AM, Erik Pearson erik@defun-web.com wrote:
Magically minimal. It looks great!
On Tue, Apr 24, 2012 at 9:36 AM, Faré fahree@gmail.com wrote:
Basically, yes. I would state, though, that I want the ASDF to work like the primary contract of REQUIRE. That is, this clause from CLHS:
require tests for the presence of the module-name in the list held by *modules*. If it is present, require immediately returns.
I think that ASDF's default mode of grokking the filesystem for changes is what I want in a development environment.
Is this what you want? Should I commit that (exporting the symbols) as asdf 2.20.22?
(in-package :asdf) (defun component-loaded-p (c) (and (gethash 'load-op (component-operation-times (find-component c nil))) t)) (defun loaded-systems () (remove-if-not 'component-loaded-p (registered-systems))) (defun require-system (s) (load-system s :force-not (loaded-systems)))
I really appreciate the responses and the new code to play with. That really is exceptional. I'll send some feedback later, hopefully today, but I have a message queue processing system to build and a web site to get up :(
Have fun!
—♯ƒ • François-René ÐVB Rideau •Reflection&Cybernethics• http://fare.tunes.org You can only find happiness by striving towards something else.