Python ctypes 的 sprintf 将任何浮点类型格式化为 b'0.000000' 或 b'5.25662e-315'
Python ctypes's sprintf formats any float type as b'0.000000' or b'5.25662e-315'
我正在尝试以最快的方式将浮点数格式化为具有尽可能少的表示形式的字符串(没有尾随 0,没有小数位,如果可以的话,没有科学记数法)。我决定试试 Python 的 ctypes
模块。
根据几个例子,我认为这个函数可以工作,但如果使用 %f
,它总是打印 b'0.000000'
,如果使用 %g
,它总是打印 b'5.25124e-315'
代码:
from ctypes import *
import msvcrt
def floatToStr3(n:float)->str:
libc = cdll.msvcrt
print("n in:", n)
sb = create_string_buffer(100)
libc.sprintf(sb, b"%g", c_float(n))
print("sb out:", sb.value)
return sb.value
import random
floatToStr3(random.random())
floatToStr3(random.random())
floatToStr3(random.random())
floatToStr3(random.random())
floatToStr3(random.random())
floatToStr3(random.random())
输出:
n in: 0.9164215022054657
sb out: b'5.25662e-315'
n in: 0.6366531536720886
sb out: b'5.23343e-315'
n in: 0.07371310207853521
sb out: b'5.1052e-315'
n in: 0.6353450576077702
sb out: b'5.23332e-315'
n in: 0.2839487624658935
sb out: b'5.18628e-315'
n in: 0.5540225836869241
sb out: b'5.22658e-315'
我有一种强烈的感觉,我只是没有正确使用 create_string_buffer,但我不知道答案是什么。使用整数进行格式化。
在 Windows 10.
上使用 Python 3.7.4
观察:
- 列表[Python 3.Docs]: ctypes - A foreign function library for Python
- 在使用 CTypes 函数时检查
[Python 3.Docs]: Built-in Types - Numeric Types - int, float, complex 声明(强调 是我的):
Floating point numbers are usually implemented using double
in C
通过将数字转换为 ctypes.c_float,它会失去精度(通常是 float 是 4 个字节长,而 double 是 8 个字节),产生的值非常接近 0 ,因此输出(也由@frost-nzcr4直觉)
- 直接调用sprintf,绝对比调用任何其他Python转换函数要快。但是我们不要忘记 Python 有很多优化,所以即使函数调用本身更快,调用所需的开销也是可能的( Python <=>C 转换)可能更高,在某些情况下整体性能比使用 Python解决方案
- 如果我们谈论速度,将
sb = create_string_buffer(100)
(和其他) 放在函数 中并不是很明智。在外面做(一次,在开始时)并且只在函数中使用它
下面是一个例子。
code00.py:
#!/usr/bin/env python
import sys
import ctypes as ct
import timeit
import random
c_float = ct.c_float
c_double = ct.c_double
cdll = ct.cdll
create_string_buffer = ct.create_string_buffer
swprintf = ct.windll.msvcrt.swprintf
swprintf.argtypes = [ct.c_wchar_p, ct.c_wchar_p, ct.c_double] # !!! swprintf (and all the family functions) have varargs !!!
swprintf.restype = ct.c_int
buf = ct.create_unicode_buffer(100)
def original(f: float) -> str:
libc_ = cdll.msvcrt
#print("n in:", f)
sb = create_string_buffer(100)
libc_.sprintf(sb, b"%g", c_double(f))
#print("sb out:", sb.value)
return sb.value.decode()
def improved(f: float) -> str:
swprintf(buf, "%g", f)
return buf.value
def percent(f: float) -> str:
return "%g" % f
def format(f: float) -> str:
return "{0:g}".format(f)
def f_string(f: float) -> str:
return f"{f}"
number_count = 3
numbers = [random.random() for _ in range(number_count)]
number = numbers[0]
def main(*argv):
funcs = [
original,
improved,
percent,
format,
f_string,
]
print("Functional tests")
for f in numbers:
print("\nNumber (default format): {0:}".format(f))
for func in funcs:
print(" {0:s}: {1:}".format(func.__name__, func(f)))
print("\nPerformance tests (time took by each function)")
for func in funcs:
t = timeit.timeit(stmt="func(number)", setup="from __main__ import number, {0:s} as func".format(func.__name__))
print(" {0:s}: {1:}".format(func.__name__, t))
if __name__ == "__main__":
print("Python {0:s} {1:d}bit on {2:s}\n".format(" ".join(item.strip() for item in sys.version.split("\n")), 64 if sys.maxsize > 0x100000000 else 32, sys.platform))
main(*sys.argv[1:])
print("\nDone.")
输出:
[cfati@CFATI-5510-0:e:\Work\Dev\Whosebug\q061231308]> "e:\Work\Dev\VEnvs\py_pc064_03.07.06_test0\Scripts\python.exe" code00.py
Python 3.7.6 (tags/v3.7.6:43364a7ae0, Dec 19 2019, 00:42:30) [MSC v.1916 64 bit (AMD64)] 64bit on win32
Functional tests
Number (default format): 0.7598920818033322
original: 0.759892
improved: 0.759892
percent: 0.759892
format: 0.759892
f_string: 0.7598920818033322
Number (default format): 0.985689825577911
original: 0.98569
improved: 0.98569
percent: 0.98569
format: 0.98569
f_string: 0.985689825577911
Number (default format): 0.613914001222863
original: 0.613914
improved: 0.613914
percent: 0.613914
format: 0.613914
f_string: 0.613914001222863
Performance tests (time took by each function)
original: 2.324927
improved: 1.8772565999999995
percent: 0.3631088
format: 0.5225973999999995
f_string: 1.2965244999999994
Done.
正如所见,内置 Python 替代方案的性能优于 CTypes 替代方案。我感到好奇的是(想知道我是否没有做错什么),f-string 变体要低得多(性能方面) 比我预期的要好。
阅读 [Python]: Python Patterns - An Optimization Anecdote 可能会很有趣。
我正在尝试以最快的方式将浮点数格式化为具有尽可能少的表示形式的字符串(没有尾随 0,没有小数位,如果可以的话,没有科学记数法)。我决定试试 Python 的 ctypes
模块。
根据几个例子,我认为这个函数可以工作,但如果使用 %f
,它总是打印 b'0.000000'
,如果使用 %g
,它总是打印 b'5.25124e-315'
代码:
from ctypes import *
import msvcrt
def floatToStr3(n:float)->str:
libc = cdll.msvcrt
print("n in:", n)
sb = create_string_buffer(100)
libc.sprintf(sb, b"%g", c_float(n))
print("sb out:", sb.value)
return sb.value
import random
floatToStr3(random.random())
floatToStr3(random.random())
floatToStr3(random.random())
floatToStr3(random.random())
floatToStr3(random.random())
floatToStr3(random.random())
输出:
n in: 0.9164215022054657
sb out: b'5.25662e-315'
n in: 0.6366531536720886
sb out: b'5.23343e-315'
n in: 0.07371310207853521
sb out: b'5.1052e-315'
n in: 0.6353450576077702
sb out: b'5.23332e-315'
n in: 0.2839487624658935
sb out: b'5.18628e-315'
n in: 0.5540225836869241
sb out: b'5.22658e-315'
我有一种强烈的感觉,我只是没有正确使用 create_string_buffer,但我不知道答案是什么。使用整数进行格式化。
在 Windows 10.
上使用 Python 3.7.4观察:
- 列表[Python 3.Docs]: ctypes - A foreign function library for Python
- 在使用 CTypes 函数时检查
[Python 3.Docs]: Built-in Types - Numeric Types - int, float, complex 声明(强调 是我的):
Floating point numbers are usually implemented using
double
in C通过将数字转换为 ctypes.c_float,它会失去精度(通常是 float 是 4 个字节长,而 double 是 8 个字节),产生的值非常接近 0 ,因此输出(也由@frost-nzcr4直觉)
- 直接调用sprintf,绝对比调用任何其他Python转换函数要快。但是我们不要忘记 Python 有很多优化,所以即使函数调用本身更快,调用所需的开销也是可能的( Python <=>C 转换)可能更高,在某些情况下整体性能比使用 Python解决方案
- 如果我们谈论速度,将
sb = create_string_buffer(100)
(和其他) 放在函数 中并不是很明智。在外面做(一次,在开始时)并且只在函数中使用它
下面是一个例子。
code00.py:
#!/usr/bin/env python
import sys
import ctypes as ct
import timeit
import random
c_float = ct.c_float
c_double = ct.c_double
cdll = ct.cdll
create_string_buffer = ct.create_string_buffer
swprintf = ct.windll.msvcrt.swprintf
swprintf.argtypes = [ct.c_wchar_p, ct.c_wchar_p, ct.c_double] # !!! swprintf (and all the family functions) have varargs !!!
swprintf.restype = ct.c_int
buf = ct.create_unicode_buffer(100)
def original(f: float) -> str:
libc_ = cdll.msvcrt
#print("n in:", f)
sb = create_string_buffer(100)
libc_.sprintf(sb, b"%g", c_double(f))
#print("sb out:", sb.value)
return sb.value.decode()
def improved(f: float) -> str:
swprintf(buf, "%g", f)
return buf.value
def percent(f: float) -> str:
return "%g" % f
def format(f: float) -> str:
return "{0:g}".format(f)
def f_string(f: float) -> str:
return f"{f}"
number_count = 3
numbers = [random.random() for _ in range(number_count)]
number = numbers[0]
def main(*argv):
funcs = [
original,
improved,
percent,
format,
f_string,
]
print("Functional tests")
for f in numbers:
print("\nNumber (default format): {0:}".format(f))
for func in funcs:
print(" {0:s}: {1:}".format(func.__name__, func(f)))
print("\nPerformance tests (time took by each function)")
for func in funcs:
t = timeit.timeit(stmt="func(number)", setup="from __main__ import number, {0:s} as func".format(func.__name__))
print(" {0:s}: {1:}".format(func.__name__, t))
if __name__ == "__main__":
print("Python {0:s} {1:d}bit on {2:s}\n".format(" ".join(item.strip() for item in sys.version.split("\n")), 64 if sys.maxsize > 0x100000000 else 32, sys.platform))
main(*sys.argv[1:])
print("\nDone.")
输出:
[cfati@CFATI-5510-0:e:\Work\Dev\Whosebug\q061231308]> "e:\Work\Dev\VEnvs\py_pc064_03.07.06_test0\Scripts\python.exe" code00.py Python 3.7.6 (tags/v3.7.6:43364a7ae0, Dec 19 2019, 00:42:30) [MSC v.1916 64 bit (AMD64)] 64bit on win32 Functional tests Number (default format): 0.7598920818033322 original: 0.759892 improved: 0.759892 percent: 0.759892 format: 0.759892 f_string: 0.7598920818033322 Number (default format): 0.985689825577911 original: 0.98569 improved: 0.98569 percent: 0.98569 format: 0.98569 f_string: 0.985689825577911 Number (default format): 0.613914001222863 original: 0.613914 improved: 0.613914 percent: 0.613914 format: 0.613914 f_string: 0.613914001222863 Performance tests (time took by each function) original: 2.324927 improved: 1.8772565999999995 percent: 0.3631088 format: 0.5225973999999995 f_string: 1.2965244999999994 Done.
正如所见,内置 Python 替代方案的性能优于 CTypes 替代方案。我感到好奇的是(想知道我是否没有做错什么),f-string 变体要低得多(性能方面) 比我预期的要好。
阅读 [Python]: Python Patterns - An Optimization Anecdote 可能会很有趣。