fix(vim.eval): evaluate Vim bool to Python bool, make num_to_str faster#603
fix(vim.eval): evaluate Vim bool to Python bool, make num_to_str faster#603justinmk merged 1 commit intoneovim:masterfrom
Conversation
dc388c4 to
b4f9e01
Compare
vim.eval to Python boolProblem:
- In Nvim, Vim bool is evaled to string, but in Vim 8+, it is evaled to
Python bool. So pynvim's legacy vim.eval is not compatible with Vim
8+.
- Previously, the argument against compatibility with Vim 8+ in this
aspect is that adding checking for Python bool would be slower. But if
we use `type(obj)` with operator `is`, it would be 3% ~ 80% faster
than the current num_to_str implementation (more complex obj seems to
have more performance gain).
This is the script I use to benchmark
```python
import timeit
num_types = (int, float)
def num_to_str(obj):
if isinstance(obj, num_types):
return str(obj)
else:
return obj
def num_to_str2(obj):
obj_type = type(obj)
if obj_type is int or obj_type is float:
return str(obj)
else:
return obj
def num_to_str3(obj):
obj_type = type(obj)
if obj_type == int or obj_type == float:
return str(obj)
else:
return
test_cases = [
(100, "Integer"),
(10.5, "Float"),
(True, "Boolean"),
("hello", "String"),
([1, 2], "List"),
({'a': 1, 'b': 2}, "Dict"), # Restored!
]
def run_benchmark():
# Using a fixed format string for perfect alignment
fmt = "{:<15} | {:<10} | {:<18} | {:<18} | {:>9}"
header = fmt.format("Input", "Type", "isinstance (s)",
"type is (s)", "type == (s)")
print("\n" + header)
print("-" * len(header))
for val, label in test_cases:
setup = "from __main__ import num_to_str, num_to_str2, num_types"
# Number of loops: 1,000,000
t1 = timeit.timeit("num_to_str(obj)", setup=setup,
number=1_000_000, globals={'obj': val, **globals()})
t2 = timeit.timeit("num_to_str2(obj)", setup=setup,
number=1_000_000, globals={'obj': val, **globals()})
t3 = timeit.timeit("num_to_str3(obj)", setup=setup,
number=1_000_000, globals={'obj': val, **globals()})
display_val = str(val)
if len(display_val) > 14:
display_val = display_val[:11] + "..."
print(fmt.format(display_val, label, f"{t1:.4f}",
f"{t2:.4f}", f"{t3:.4f}"))
if __name__ == "__main__":
run_benchmark()
```
And the benchmark result:
```
Input | Type | isinstance (s) | type is (s) | type == (s)
------------------------------------------------------------------------------------
100 | Integer | 0.1125 | 0.1095 | 0.1118
10.5 | Float | 0.3256 | 0.2512 | 0.2791
True | Boolean | 0.0731 | 0.0604 | 0.0788
hello | String | 0.1005 | 0.0600 | 0.0793
[1, 2] | List | 0.0940 | 0.0602 | 0.0755
{'a': 1, 'b... | Dict | 0.1080 | 0.0599 | 0.0759
```
So clearly using `type(obj)` with `is` is the fastest way.
Solution:
- Use type(obj) with is operator is to check for exact number
|
I agree we should making breaking changes for correct types. |
|
Btw, I'd like to say the main reason I make this PR: I have never found the explanation in #523 (comment) persuasive. This is Vim 7.3 document
By "vim internal expression evaluator", I am sure the doc means Vimscript evaluator and not Python one. I don't know enough about Vimscript internal, but if it evaluates to a number then
But this part of the comment seems to mistake Vimscript evaluator with Python evaluator. Vim document only says that Vim number (which is the result of the "vim internal expression evaluator") must be converted to Python string, not that Python number must be converted to Python string (imagine in the future Vim could add a rule that Vim funcref will be |
Problem:
Python bool.
aspect is that adding checking for Python bool would be slower. But if
we use
type(obj)with operatoris, it would be 3% ~ 80% fasterthan the current num_to_str implementation (more complex obj seems to
have more performance gain).
This is the script I use to benchmark
And the benchmark result:
So clearly using
type(obj)withisis the fastest way.Solution:
@justinmk