SBCL分析显示我的一个Common Lisp哈希表函数耗费了大量时间.该函数比较两个哈希表以确定它们是否具有相同的键:
(defun same-keys (ht1 ht2)
"Returns t if two hash tables have the same keys."
(declare (hash-table ht1 ht2))
(when (= (hash-table-count ht1) (hash-table-count ht2))
(maphash (lambda (ht1-key ht1-value)
(declare (ignore ht1-value))
(unless (gethash ht1-key ht2)
(return-from same-keys nil)))
ht1)
t))
Run Code Online (Sandbox Code Playgroud)
有没有办法加速这个,因为哈希表总是#'eql
带fixnum
键?我也在加载lparallel
库,但在这种情况下以某种方式并行化函数是否有意义?
编辑:哈希表的大小可以是大约10到100个条目.ht键范围从100扩展到999,999,999,999,但在此范围内实际使用的总可能的fixnums是稀疏的.每个ht值都是t或列表.所有哈希表的键值关联都在加载时设置.通过复制现有哈希表并逐步添加或删除条目,在运行时创建新哈希表.常规哈希表的读取,写入和复制似乎不是问题.
如何正确指定可扩展向量的common-lisp类型(即,vector-push-extend可接受),因此可以复制它.例如,如果向量定义为:
(defparameter v (make-array 2
:initial-contents '((a (b)) (c (d) e))
:adjustable t
:fill-pointer t))
Run Code Online (Sandbox Code Playgroud)
我复制它的简单(不正确)方法是:
(map 'array #'copy-tree v)
Run Code Online (Sandbox Code Playgroud)
但这会在sbcl中生成一个类型错误.一个正确的序列类型规范可以使这个工作吗?
我熟悉从文件中收集 Lisp 对象的基本模板,如下所示:
(with-open-file (stream "filename.lisp")
(loop for object = (read stream nil 'eof)
until (eq object 'eof)
collect object))
Run Code Online (Sandbox Code Playgroud)
但我不确定如何将其转换为从字符串中收集对象,例如使用read-from-string
. 那么您是否必须跟踪字符串中停止位置的索引?另外,如何避免与输入中的或eof
等任何其他合法 Lisp 对象发生名称冲突?nil
t
在搜索nil
序列中的使用find
总是返回NIL
。
(find nil #(a b nil c)) -> NIL
(find nil #(a b c)) -> NIL
Run Code Online (Sandbox Code Playgroud)
如果序列是列表,则相同。
但是,member
运行符合我的预期:
(member nil '(a b nil c)) -> (NIL C)
Run Code Online (Sandbox Code Playgroud)
为什么find
设计为以这种方式运行?
请注意,它的position
工作与我期望的一样:
(position nil #(a b nil c)) -> 2
(position nil #(a b c)) -> NIL
Run Code Online (Sandbox Code Playgroud) lparallel库中队列的基本讨论,网址为https://z0ltan.wordpress.com/2016/09/09/basic-concurrency-and-parallelism-in-common-lisp-part-4a-parallelism-using-lparallel- basics /#channels说,该队列“启用在工作线程之间传递消息”。下面的测试使用一个共享队列来协调主线程和从属线程,其中主线程在退出之前只是等待下一个线程的完成:
(defun foo (q)
(sleep 1)
(lparallel.queue:pop-queue q)) ;q is now empty
(defun test ()
(setf lparallel:*kernel* (lparallel:make-kernel 1))
(let ((c (lparallel:make-channel))
(q (lparallel.queue:make-queue)))
(lparallel.queue:push-queue 0 q)
(lparallel:submit-task c #'foo q)
(loop do (sleep .2)
(print (lparallel.queue:peek-queue q))
when (lparallel.queue:queue-empty-p q)
do (return)))
(lparallel:end-kernel :wait t))
Run Code Online (Sandbox Code Playgroud)
这可以按预期的方式产生输出:
* (test)
0
0
0
0
NIL
(#<SB-THREAD:THREAD "lparallel" FINISHED values: NIL {10068F2B03}>)
Run Code Online (Sandbox Code Playgroud)
我的问题是我是否正确或完全使用了lparallel的队列功能。似乎队列只是使用全局变量来保存线程共享对象的替代品。使用队列的设计优势是什么?通常为每个提交的任务分配一个队列(假设任务需要进行通信)是一种好习惯吗?感谢您提供更深入的见解。
在Lparallel API中,终止所有线程任务的推荐方法是使用停止内核(lparallel:end-kernel)
。但是,当线程正在阻塞时(例如,(pop-queue queue1)
等待某个项目出现在队列中),当内核停止时,该线程仍将处于活动状态。在这种情况下(至少在SBCL中),内核关闭偶尔(但并非每次)失败,并显示以下信息:
debugger invoked on a SB-KERNEL:BOUNDING-INDICES-BAD-ERROR in thread
#<THREAD "lparallel" RUNNING {1002F04973}>:
The bounding indices 1 and NIL are bad for a sequence of length 0.
See also:
The ANSI Standard, Glossary entry for "bounding index designator"
The ANSI Standard, writeup for Issue SUBSEQ-OUT-OF-BOUNDS:IS-AN-ERROR
debugger invoked on a SB-SYS:INTERACTIVE-INTERRUPT in thread
#<THREAD "main thread" RUNNING {10012E0613}>:
Interactive interrupt at #x1001484328.
Run Code Online (Sandbox Code Playgroud)
我假设这与阻塞线程无法正确终止有关。关闭内核之前,应该如何正确终止阻塞线程?(API表示kill-tasks
仅应在特殊情况下使用,我认为这种情况不适用于这种“正常”关闭情况。)
Common Lisp序列函数remove-duplicates
在每个多重性后面都保留一个元素。以下类似功能的目标remove-equals
是删除所有重复项。
但是,我想使用内置函数remove-if
(而不是迭代)和SBCL的哈希表功能用于:test函数,以将时间复杂度保持在O(n)。迫在眉睫的问题是SBCL相等性测试需要是全局的,但是该测试还需要依赖于的key
参数remove-equals
。可以满足两个要求吗?
(defun remove-equals (sequence &key (test #'eql) (start 0) end (key #'identity))
"Removes all repetitive sequence elements based on equality test."
#.(defun equality-test (x y)
(funcall test (funcall key x) (funcall key y)))
#.(sb-ext:define-hash-table-test equality-test sxhash)
(let ((ht (make-hash-table :test #'equality-test)))
(iterate (for elt in-sequence (subseq sequence start end))
(incf (gethash (funcall key elt) ht 0)))
(remove-if (lambda (elt)
(/= 1 (gethash elt ht)))
sequence :start …
Run Code Online (Sandbox Code Playgroud) 包含 coredump 响应的SO 帖子展示了如何将编译器策略应用于 ASDF 系统的组件文件:
(defsystem simple-system
:serial t
:around-compile (lambda (next)
(proclaim '(optimize (debug 3)
(safety 3)
(debug 3)
(speed 0)))
(funcall next))
:components ((:module "src"
:components
(...))))
Run Code Online (Sandbox Code Playgroud)
它还提到您可以“隐藏”单个文件,但这将如何工作。这让我感到困惑,因为next
在 lambda 表达式中绑定到一个闭包。由于我只需要对几个组件文件应用优化,你如何将这些文件名赋予:around-compile
?
在Common Lisp中引用函数似乎有许多不同的方法:
通过符号,其中符号出现(未引用)作为形式的汽车(1+ 2) => 3
,或在功能参数位置(mapcar '1+ '(1 2 3)) => (2 3 4)
;
通过功能对象,其中,所述(解释或编译)函数对象可以出现在函数自变量的位置中(mapcar #'1+ '(1 2 3)) => (2 3 4)
或(mapcar (symbol-function '1+) '(1 2 3)) => (2 3 4)
,而不是作为一种形式如在车上(#'1+ 2) => error
或((symbol-function '1+) 2) => error
;
通过lambda表达式,其中lambda表达式显示为lambda形式的汽车((lambda (x) (1+ x)) 2) => 3
,或者在函数参数位置中显示(mapcar (lambda (x) (1+ x)) '(1 2 3)) => (2 3 4)
[但是,Hyperspec不会将lambda表达式识别为"函数指示符"].
在这三种"方式"中,对我而言,第一种似乎有些不合适,因为它似乎使Common Lisp运算符仅仅评估其参数一次的基本准则复杂化.如果在上面的示例'1+
中进行评估,它将生成符号1+ …
以下是旨在用作(lifo)堆栈或(fifo)队列的结构
(defstruct jvector
(vector (make-array 0 :adjustable t :fill-pointer 0) :type (array * (*)))
(start 0 :type (integer 0 *)))
Run Code Online (Sandbox Code Playgroud)
内容的范围从jvector-start到jvector-vector填充指针。我希望能够用类似的东西指定内容的元素类型
(defun create-jvector (&key (element-type t))
(make-jvector :vector (make-array 0 :element-type element-type :adjustable t :fill-pointer 0)
:start 0))
Run Code Online (Sandbox Code Playgroud)
并用
(defun push-jvector (elt jvec)
(vector-push-extend elt (jvector-vector jvec)))
Run Code Online (Sandbox Code Playgroud)
但是,create-jvector中的元素类型将被忽略。例如,
* (defparameter v (create-jvector :element-type 'integer))
V
* v
#S(JVECTOR :VECTOR #() :START 0)
* (push-jvector 1 v)
0 ;OK result
* v
#S(JVECTOR :VECTOR #(1) :START 0)
* (push-jvector 'a v) …
Run Code Online (Sandbox Code Playgroud)