LISP - 字节数组的快速输出
LISP - Fast output of byte array
我正在为一种 LISP 语言制作编译器,总体目标是让编译器从原始语言生成 LISP 代码。尝试衡量生成代码的性能,我发现它在打印字符串方面严重不足。
在原始语言中,字符是字节的算术值,因此字符串是字节的数组,字节的值对应的字符的值为字节的ascii码。 "printable" 字节数组必须以 null 结尾。因此,要将字节数组打印为字符串,我必须在打印之前将原始数组的元素映射为字符。处理这个的函数如下:
(defun writeString (X &AUX (NPOS 0) (i 0))
(declare (type (simple-VECTOR fixnum *) x))
(declare (type fixnum NPOS i))
(SETF NPOS (POSITION 0 X))
(IF (NOT NPOS)
(SETF NPOS (LENGTH X)))
(princ (MAKE-ARRAY NPOS
:INITIAL-CONTENTS (map 'vector
#'code-char
(SUBSEQ X 0 NPOS))
:ELEMENT-TYPE 'base-char)))
并将其注入到生成的代码中。
运行 带有 time
的示例代码,我发现 princ
部分在执行过程中会导致很多 consing,这会减慢速度。当在 make-array...
的位置放置一个静态字符串时,没有减速也没有 consing,所以我猜这就是损坏的部分。
在编译时,我已将速度标志设置为全速,字节值目前在生成的代码中声明为 fixnum。
任何人都可以指出一种更好的方法来将我的字节数组打印为字符串,同时避免过多的 consing 吗?
我可以从一开始就将字节存储为字符,但这会导致语言中将它们视为数字的部分由于需要转换而变慢。
你的代码有问题
您的代码:
(defun writeString (X &AUX (NPOS 0) (i 0))
(declare (type (simple-VECTOR fixnum *) x))
(declare (type fixnum NPOS i))
(SETF NPOS (POSITION 0 X))
(IF (NOT NPOS)
(SETF NPOS (LENGTH X)))
(princ (MAKE-ARRAY NPOS
:INITIAL-CONTENTS (map 'vector
#'code-char
(SUBSEQ X 0 NPOS))
:ELEMENT-TYPE 'base-char)))
代码中有几个错误:
i
未使用
- 第一个类型声明语法无效
- NPOS 声明错误。你定义它为 FIXNUM,但它可以是 NIL。
有一堆编程错误:
- 如果你只想输出字符,就不需要分配任何数组。
- 就算要生成数组,一次也可以
- X 不是一个好的字符串名称
一个简单的解决方案:
(defun writestring (bytestring)
(loop for byte across bytestring
while (plusp byte)
do (write-char (code-char byte))))
类型声明的版本可以是:
(defun writestring (bytestring)
(declare (vector bytestring))
(loop for byte of-type (integer 0 255) across bytestring
while (plusp byte)
do (write-char (code-char byte))))
代替(integer 0 255)
也可以使用(unsigned-byte 8)
。
关于生成向量:
让我们也看看您是如何尝试创建数组的:
您使用 make-array 创建了一个数组,使用了另一个数组中的内容。
为什么不告诉 MAP 生成正确的数组?
CL-USER 46 > (map '(vector base-char) #'code-char #(102 111 111 98 97 114))
"foobar"
现在,如果您出于某种原因想要分配数组:
- 做一次
- 将内容映射到生成的数组中。为此使用
map-into
。它将以较短的序列停止。
示例:
CL-USER 48 > (let ((bytestring #(102 111 111 98 97 114 0 100 100 100)))
(map-into (make-array (or (position 0 bytestring)
(length bytestring))
:element-type 'base-char)
#'code-char
bytestring))
"foobar"
您可以依赖 write-sequence
,它有望被优化以写入一系列字符或字节。它还接受一个 :end
参数,这对于分隔写入字符串的结尾很有用。
我怀疑您是否真的需要使用文字向量(它们总是 simple-vector
),但如果是这样,您可能想要更改它们。您可以在阅读时执行此操作:
(let ((input #.(coerce #(102 111 111 98 97 114 0 100 100 100)
'(vector (mod 256)))))
(write-sequence (map '(vector base-char)
#'code-char
input)
*standard-output*
:end (position 0 input)))
我从来没有使用过类似下面的东西,但你也可以在字符和字节模式下打开同一个文件,并在需要时切换:
(with-open-file (out-c #P"/tmp/test"
:if-exists :supersede
:direction :output)
(with-open-file (out-8 #P"/tmp/test"
:element-type '(unsigned-byte 8)
:direction :output
:if-exists :append)
(format out-c "Hello [")
(file-position out-8 (file-position out-c))
(write-sequence #(102 111 111 98 97 114) out-8)
(file-position out-c (file-position out-8))
(format out-c "]")))
它在 /tmp/test 中打印 "Hello [foobar]"
,它似乎可以处理多字节字符,但您可能需要进行更多测试。
我正在为一种 LISP 语言制作编译器,总体目标是让编译器从原始语言生成 LISP 代码。尝试衡量生成代码的性能,我发现它在打印字符串方面严重不足。
在原始语言中,字符是字节的算术值,因此字符串是字节的数组,字节的值对应的字符的值为字节的ascii码。 "printable" 字节数组必须以 null 结尾。因此,要将字节数组打印为字符串,我必须在打印之前将原始数组的元素映射为字符。处理这个的函数如下:
(defun writeString (X &AUX (NPOS 0) (i 0))
(declare (type (simple-VECTOR fixnum *) x))
(declare (type fixnum NPOS i))
(SETF NPOS (POSITION 0 X))
(IF (NOT NPOS)
(SETF NPOS (LENGTH X)))
(princ (MAKE-ARRAY NPOS
:INITIAL-CONTENTS (map 'vector
#'code-char
(SUBSEQ X 0 NPOS))
:ELEMENT-TYPE 'base-char)))
并将其注入到生成的代码中。
运行 带有 time
的示例代码,我发现 princ
部分在执行过程中会导致很多 consing,这会减慢速度。当在 make-array...
的位置放置一个静态字符串时,没有减速也没有 consing,所以我猜这就是损坏的部分。
在编译时,我已将速度标志设置为全速,字节值目前在生成的代码中声明为 fixnum。
任何人都可以指出一种更好的方法来将我的字节数组打印为字符串,同时避免过多的 consing 吗?
我可以从一开始就将字节存储为字符,但这会导致语言中将它们视为数字的部分由于需要转换而变慢。
你的代码有问题
您的代码:
(defun writeString (X &AUX (NPOS 0) (i 0))
(declare (type (simple-VECTOR fixnum *) x))
(declare (type fixnum NPOS i))
(SETF NPOS (POSITION 0 X))
(IF (NOT NPOS)
(SETF NPOS (LENGTH X)))
(princ (MAKE-ARRAY NPOS
:INITIAL-CONTENTS (map 'vector
#'code-char
(SUBSEQ X 0 NPOS))
:ELEMENT-TYPE 'base-char)))
代码中有几个错误:
i
未使用- 第一个类型声明语法无效
- NPOS 声明错误。你定义它为 FIXNUM,但它可以是 NIL。
有一堆编程错误:
- 如果你只想输出字符,就不需要分配任何数组。
- 就算要生成数组,一次也可以
- X 不是一个好的字符串名称
一个简单的解决方案:
(defun writestring (bytestring)
(loop for byte across bytestring
while (plusp byte)
do (write-char (code-char byte))))
类型声明的版本可以是:
(defun writestring (bytestring)
(declare (vector bytestring))
(loop for byte of-type (integer 0 255) across bytestring
while (plusp byte)
do (write-char (code-char byte))))
代替(integer 0 255)
也可以使用(unsigned-byte 8)
。
关于生成向量:
让我们也看看您是如何尝试创建数组的:
您使用 make-array 创建了一个数组,使用了另一个数组中的内容。 为什么不告诉 MAP 生成正确的数组?
CL-USER 46 > (map '(vector base-char) #'code-char #(102 111 111 98 97 114))
"foobar"
现在,如果您出于某种原因想要分配数组:
- 做一次
- 将内容映射到生成的数组中。为此使用
map-into
。它将以较短的序列停止。
示例:
CL-USER 48 > (let ((bytestring #(102 111 111 98 97 114 0 100 100 100)))
(map-into (make-array (or (position 0 bytestring)
(length bytestring))
:element-type 'base-char)
#'code-char
bytestring))
"foobar"
您可以依赖 write-sequence
,它有望被优化以写入一系列字符或字节。它还接受一个 :end
参数,这对于分隔写入字符串的结尾很有用。
我怀疑您是否真的需要使用文字向量(它们总是 simple-vector
),但如果是这样,您可能想要更改它们。您可以在阅读时执行此操作:
(let ((input #.(coerce #(102 111 111 98 97 114 0 100 100 100)
'(vector (mod 256)))))
(write-sequence (map '(vector base-char)
#'code-char
input)
*standard-output*
:end (position 0 input)))
我从来没有使用过类似下面的东西,但你也可以在字符和字节模式下打开同一个文件,并在需要时切换:
(with-open-file (out-c #P"/tmp/test"
:if-exists :supersede
:direction :output)
(with-open-file (out-8 #P"/tmp/test"
:element-type '(unsigned-byte 8)
:direction :output
:if-exists :append)
(format out-c "Hello [")
(file-position out-8 (file-position out-c))
(write-sequence #(102 111 111 98 97 114) out-8)
(file-position out-c (file-position out-8))
(format out-c "]")))
它在 /tmp/test 中打印 "Hello [foobar]"
,它似乎可以处理多字节字符,但您可能需要进行更多测试。