Closed ElPrudi closed 1 year ago
Looks good! The only thing I would prefer name similar to minify
, speedify
, for example.
Will you help me with transformations? You can put them to 🐊Putout Editor, as I see most of them pretty simple and can be made using 🦎PutoutScript only.
And some of them we already have:
[...arr]
with arr.slice()
convert-array-copy-to-slice;Number(x)
to +x
minify/types
Boolean(x)
with !!x
minify/types
Array.from(x)
with [...x]
minify/types
Math.floor(x)
with ~~x
minify/simplify-floor
String(x)
and x.toString()
with ${x}
minify/apply-template-literal
This one is good for @putout/plugin-math
:
x * 0.333333
or x ** (1 / 3)
with Math.cbrt(x)
This one is good for @putout/plugin-minify:
instanceof
of primitive types with typeof
We can just add speed comparison to documentation.
You can choose the name as you like. I have no problem with that.
I could try to help you with the transformations, but I can't promise much. Not everything has to be included though, they are just ideas.
Replace all
instanceof
of primitive types withtypeof
Not sure if it's safe transformation:
Extending primitive types and built-ins is widely considered an anti-pattern and is extremely slow and memory intensive and is extremely rare to find today. For example, it costs 1 KB of heap to add a function to Math
while a standalone function costs 200 B. I once had all the values written down, but I can't access the list right now, but I'll put them in here later.
So it is a great idea to add a rule to not extend it.
So it is a great idea to add a rule to not extend it.
How the code can look like? Before and after transformation.
You know so much about optimizations, could you help me to optimize @putout/compare
?, this is the function used most of the time in all plugins. I did my best, but I feel that I could miss something. Optimizing this function will speed up 🐊Putout drastically.
So it is a great idea to add a rule to not extend it.
How the code can look like? Before and after transformation.
Extensions of built-ins look like this:
<Built-in>.prototype.<function> = {} // instance method
<Built-in>.prototype.<property> = 5 // instance property
<Built-in>.<function> = {} // static method
<Built-in>.<property> = 5 // static property
If you extend it with a class, it looks like this:
class <Name> extends <Built-In>
It's not fixable, you should just forbid it like eslint-plugin-no-use-extend-native does.
While it's ok to extend Error
, Array
, Set
, Map
, WeakMap
, WeakSet
, the TypedArrays or other data structures, extending Math
, Boolean
, Number
and String
should be avoided.
You know so much about optimizations, could you help me to optimize
@putout/compare
?, this is the function used most of the time in all plugins. I did my best, but I feel that I could miss something. Optimizing this function will speed up 🐊Putout drastically.
I can look into it. It's what I enjoy the most. But you should first consider using TypeScript and build your plugins with esbuild. This will significantly decrease the import time of the plugins and will speed up stuff significantly more than you think.
This one is good for
@putout/plugin-math
:
- Replace
x * 0.333333
orx ** (1 / 3)
withMath.cbrt(x)
One thing about that: I'd suggest that the 0.333333...
should be checked for any length, as Math.cbrt(x)
is much more precise.
It's not fixable, you should just forbid it like eslint-plugin-no-use-extend-native does.
All 🐊Putout rules are fixable, and if this features exists in eslint-plugin-no-use-extend-native
it can be just used as it is.
But you should first consider using TypeScript and build your plugins with esbuild.
TypeScript is too slow, I'll wait while it will be rewritten to Rust
. Also all this types doesn't help me at all. TypeScript gives to much freedom in naming methods and options: when you know there is autocomplete, you can name a thing with aLotOfWordsSinceTheyWillBeCompleted
, and I'm building simple API, which can be memorized easily. Also I'm using VIM, so don't know how TS help me.
The idea of independent plugins, that can be loaded when needed it is one of design choices, anyways I have bundle support, with help of rollup
, it is used in Mobile Putout Editor.
TypeScript is for readability only. I don't know what form your objects, options, and arguments have. It also helps you narrow down types and find bugs that aren't so obvious. Even if it's a simple API, it's hard to fix something through a PR if people don't understand what a function does and what types are used, thus taking more time to understand what's what than to actually fix the code. You say it's easy to understand, but you don't even have documentation comments.
I didn't say bundle every plugin into one big bundle. I meant that you need to minify your plugins to save import time. The file you linked is 6.56 KB in size. I parsed it with esbuild, and now it is only 4.2 KB with the same functionality. That doesn't sound like much, but it saves a lot of import time doing it with each plugin separately.
The reason I said you need to parse it with esbuild is because it is the fastest minifier and bundler out there. It even has a rollup plugin if you want to bundle your plugins with rollup.
I know about esbuild, it has a lot of issues, and things that it still do not support.
About TS: I also don’t like it’s messages like: “ambient context”, error codes, and inability to fix things. Take a look at commits that points to issues you create. They all are similar, they all very simple and have tests. Types not needed at all for this cases.
About comments, I don’t write them, I write documentation. Everything in README
files.
About imports, as I said would be great to optimize compare
function. Look 🐊Putout right now has a lot of scenarios, that includes: CLI, editor, mobile editor, coverage , mocking, minifiier, Deno support etc. Some of them use bundling, some not. There is no need to switch language (except to Rust
, maybe in future) since I have everything that I need to move very fast, with fast tests and fast way to make transformations.
It's simple: if you don't explain your functions or types in your code, you can't expect anyone to help you fix something you can't. Imagine that babel exposes its utility functions, but has no documentation comments because it is "a simple API".
Minimizing your code is essential when you load so many plugins at once. Because otherwise you will also load your comments, lengthy variable and function names and empty lines. The JS interpreter has to load the entire file before it can decide what to import and what not. And from the example above, loading 4.2 KB instead of 6.56 KB is faster. I only named esbuild because it's the fastest. You can use terser, swr or any other minifier.
You keep complaining that TS is slow, lengthy and incomprehensible. I can tell you that very loose TypeScript exists. You don't need to write verbose and complex types. "Ambient context" is only a thing if you include declaration files. Don't. You don't need declaration files to use types. You can just import them like normal things. TypeScript even has type
imports to remove those imports after compilation.
OK, if you help me with .dts
file for plugins api and going to write plugins using TypeScript, I’ll merge it. We can start from the simplest type of plugin: Replacer, from 💚@putout/engine-runner
written in 🦎PutoutScript:
export const report = () => 'always string';
export const replace = () => ({
'const always = "string"': 'const also = "string"',
});
☝️You can read more about Plugins API and even bootstrap your own plugin with yo putout
☝️There is a yellow Replacer on schema
How types can look like? Would be great if they can check what is exported, since report
is always Report
type arrow function
that returns a string
and cannot have any other type.
About bundling:
I know that this speeds things up, but I need a way of simple debug for such big application, it makes life easier, anyways you can use 🐊Putout as library and import only plugins you need, and also use esbuild
it will speed up lint for you drastically.
Also I need to feel the speed with real imports, so I can speed up the most important things, like compare
(run, not load). This is HOT function, and speed things up win influence all consumers, not only CLI, so the win is really important 80% Pareto Principle.
☝️ Here is brunch with first class support of TypeScript, without building to separate directory, like in Deno feature/typescript-support-for-plugins
. But I don’t like both things: slow speed and way 🐊Putout plugins looks like.
I'll help you with the compare plugin as most of its functions are quite easy to understand. The thing is: You don't need a .d.ts file. There are multiple tools that can automatically generate the .d.ts files for the current project. You only need to copy the documentation comments of your functions afterwards. We only need to write normal TypeScript. The easiest way to do it is by using a rollup config, as it has plugins for type checking and automatic declaration file generation.
I can help you with the setup. We can try minifying and bundling either with esbuild or terser. Or you can make your own rollup plugin of your bundler and minifier. Here is how to create your own rollup plugin.
Thank you, but I don’t want build anything in this project, I know how it slow, and I don’t need this. As I said, I need a way to debug easily and fast tests. Right now 4000 tests passed in less then 30 seconds. This is the reason why I don’t want support recast
anymore it has very slow tests, because of build step and TS which never helps to fix bugs. I like JavaScript and maximum what I add is .dts
file and even that I don’t need.
The thing is for all issues you created TS would help to fix none of them.
Other than that, I only found three main performance caveats:
You create static objects for each function call, even though they are readonly.
let x
for (let i = 0; i < 1000000; i++) xyz = { name: ANY}.name // 4.002ms
const any = { name: ANY}
for (let i = 0; i < 1000000; i++) xyz = any.name // 1.303ms
You don't hoist variables in your loops
for (let i = 0; i < 1000000; i++) { // 2.501ms
const x = i
const y = i + 1
const z = i + 2
}
let x1, y1, z1 for (let i = 0; i < 1000000; i++) { // 1.2ms x1 = i y1 = i + 1 y1 = i + 2 }
3. You rely to much on objects and arrays. You create them, just to destructure them again later. Destructuring is slow. Everytime you use `{...}` or `[...]` costs time.
```ts
const obj_ID = { name: 'ID', key: '123' }
for (let i = 0; i < 1000000; i++) { // 2.874ms
const { name, key } = obj_ID
}
let name, key
for (let i = 0; i < 1000000; i++) { // 1.249ms
name = obj_ID.name
key = obj_ID.key
}
These are the only things I can find. More structural performance issues require a deeper knowledge of putout.
Could you please point me on places in code, that can be improved?
Closed due to a long time of inactivity 🏝 Feel free to reopen when you have new ideas.
@putout/plugin-speedify
ideaWhy ?
The idea of this plugin is that certain common micro-optimizations, which are easy to overlook, are automatically corrected and / or adjusted. Alone, these optimizations are not worth mentioning, but in larger code projects, where they appear hundreds or thousands of times, can make up for a few seconds of computing time.
In the following, these small optimizations are listed categorically and can be expanded in the replies. This plugin, if well developed, could bring a big advantage over other Eslint plugins and thus a better overall code quality level.
Some of these ideas are already housed in other plugins, however it would be recommended to move those with the goal of increasing performance to this plugin.
Rules
Number
Number(x)
to+x
:for (let i = 0; i < 1000000; i++) +i // 2.614ms
Math.floor(x)
with~~x
x ** 0.5
withMath.sqrt(x)
x * 0.333333
orx ** (1 / 3)
withMath.cbrt(x)
Math.hypot(x, y)
withMath.sqrt(x * x + y * y)
String
String(x)
andx.toString()
with${x}
str.startsWith('x')
withstr[0] === 'x'
Boolean and Checks
Boolean(x)
with!!x
instanceof
of primitive types withtypeof
for (let i = 0; i < 1000000; i++) i instanceof String // 8.233ms for (let i = 0; i < 1000000; i++) typeof i === 'string' // 2.401ms
for (let i = 0; i < 1000000; i++) i instanceof Boolean // 8.891ms for (let i = 0; i < 1000000; i++) typeof i === 'boolean' // 2.449ms
for (let i = 0; i < 1000000; i++) i instanceof BigInt // 8.192ms for (let i = 0; i < 1000000; i++) typeof i === 'bigint' // 2.46ms
for (let i = 0; i < 1000000; i++) i instanceof Symbol // 10.366ms for (let i = 0; i < 1000000; i++) typeof i === 'symbol' // 2.481ms
Array
[...arr]
witharr.slice()
arr.concat(arr2)
withArray.prototype.push.apply(arr, arr2)
Array.from(x)
with[...x]
const [x, y] = [a, b]
withconst x = a; const y = b
Objects
const x = {...y, ...z}
withconst x = Object.assign({}, y, z)
x = {...x, ...y}
withObject.assign(x, y)