It is a command that lets you append simple text to simple text files.
It's stupid simple: you state a piece of text name a file to append it to and AHK will append the text or create a new file with that text.
Code: Select all
Loop, 99
FileAppend, %A_Index%, numbers.txt
It's really simple but also really really bad code that can cause major issues on newer systems.
Code: Select all
file := FileOpen("numbers.txt","a")
Loop 99
file.write(A_Index)
file.close()
Each FileAppend command does the same as a few actions with a fileObject:
Code: Select all
Loop 99 {
file := FileOpen("numbers.txt", "a")
file.write(A_Index)
file.close()
}
You might just think the closing an opening of a file is neglecteable - however that's not the case.
You are dealing with the filesystem. A special piece of hardware that is heavily optimized and backed up and relevant for security.
Opening and closing something doesn't have to happen fast nor does it have to happen that often.
And therefore it's made to last long - however once opened you can write a lot to a file in a fast way.
And when you close it all this will have been written to the file on the disk.
Here are the 3 biggest problems that opening and closing a file repeadetly like this can cause:
1. Not using the buffer
While the file is open you can always tell Windows to write new data to the file.
Windows will then take that data and slowly write it to the file - however it will tell you to move on with your program before it even finishes writing.
The data is then buffered before it's slowly written to the file. When you want to write more data while the file is still open it will just get added to the buffer.
Windows will only tell your program to wait when this buffer gets filled and it can't buffer anymore - however it's unlikely that this will happen easily.
This buffering process is important to make your program faster. It is also used to increase the lifetime of SSDs.
Closing the file will force windows to completely flush the buffer onto the disk before execution continues.
Doing this cycle of opening and closing excessively completely fails to make use of this feature.
This can massively decrease the performance of your script while also damaging the hardware of your users on the long run.
2. Shadowcopies
Starting with one of the newer Windows Versions (I think Vista or 7) Windows keeps backups of all the old versions of your files.
This is so that you can restore them or so that Windows can analyze and optimize it's programs.
Regardless of whether you like this feature or not - a fact is that this features is active in many if not most Windows PCs.
And don't even dream about your users disabling this feature just for your program.
Each time you close a file Windows will back up this new version of the file in addition to removing the old one.
If you are appending like this Windows will have to start moving around some serious data after your file gets large enough.
Each successive close operation will take longer. a file with 99 lines might be fine, but what about 300k lines, what if each new line contained 100s of characters instead of 1 or 2?
Once again this might decrease the durability of your users hardware and it massively decreases performance.
3. Other programs
If you are actively using this PC and not only using it to produce a single AutoHotkey script you might have other programs installed.
People commonly have anti-virus programs installed and these commonly tend to take a look at files programs write.
People might also have a synchronised folder like dropbox or similar and these tend to take a look at new files programs write to upload them to their cloud.
Of course while the file is open the cloud folder won't move an inch. The anti-virus program might also jump a bit into action and take a look at the file thats is being written.
But honestly only if there is enough resources and your PC doesn't have anything else to do.
However after the file is closed these programs jump to action and scream: "Now it is my time to block your resources and hog your PC to find out if this is dangerous" or "Now it is my time to block your resources and hog your internet to upload this new file version on your cloud folder" upon which the Anti-Virus program once again jumps to action to check this new action thats being taken.
If you close and open repeadetly each program will jump to action after every single time and do it's thing again.
You won't be able to write to your file until these programs are done with your file.
In the best case your program will become extraordinarily slow. Consider that the Anti-Virus program performs an heuristic search on the entire file that you just touched with FileAppend and the process that is still running.
And perhaps it might find these consecutive writes supsicious and it might do a complete search each time. Multiple minutes for each new open and close.
Perhaps it might even find that this behavior is a bother in itself and toss the program from the PC and give a malware warning.
In the worst case FileAppend will fail silently - as all things tend to fail silently in AHK v1.
Your dropbox provider might even block you from uploading new data automatically because you are using too much of their bandwidth.
You don't really want to damage the users of your programs or?
Open your files when you start writing to them - Close them after there is nothing that you will write to them anymore.
Additional interesting results:
Keeping a save logfile:
Using File.write will not immediately write to the file - instead it will write to the file when it pleases.
When reading from the file object that you are writing to it will immediately write to the file.
When crashing windows will not write the buffer thats currently held by the fileObject onto the disk - meaning that you might loose the data thats inside there even if you used file.write.
In order to prevent that you can use file.read(0) to immediately write the buffer to the disk.
Further research is necessary on that matter though as I do not know the impacts performance wise.
The batchsize problem:
Now you might ask yourself "why don't we just write the entire text that is created to a variable and then after the text creation is done we write it to the disk?".
And in my opinion that's fine. It's a good solution - however there are a few situations where you might prefer using file.write during the loop:
You have a lot of data and cannot hold it all inside the memory.
You want to make sure at least part of it is saved even when crashes occur during text generation.
You generate a lot of data that you need to write to the disk as fast as possible.
Generally however there isn't a lot of difference between either alternative - so you might just stick with the one that you like more.
I think both times you are just being lazy - which isn't a bad thing, you need to avoid extra work to get things done.
Of course when looping 99 times and just writing the number down each iteration we dont generate a lot of data.
So it's easy to imagine that the system wont write to the disk until we close the file anyways - you will call file.write every time but it won't have any effect.
But what if we loop 4.000.000 times? Sure the first 100 times the system won't act on us calling file.write but what about the 101th time?
It just might be enough data to cause the system to write to the disk. However that also means that we made 100 useless attempts at doing that.
Of course the system will make the writes highly optimized with regards to many things and considering things we cannot possibly imagine.
However these considerations take time. If we want maximum performance we should avoid using this too often.
So what we do in this case is pack a few iterations into a batch and notify the system of the results of this whole batch at once.
Using this setup 'calling file.write every iteration' would be a batch-size of 1 and 'calling it once at the end' is a batch size of infinity.
Both are equaly lazy and probably equally bad. Example code:
Code: Select all
#NoEnv
SetBatchLines , -1
file := FileOpen("test.txt", "w")
batchBuffer := ""
batchSize := 4000
maxNr := 1000000
Loop %maxNr% {
e .= A_Index
if (!mod(A_Index, batchSize)||A_Index=maxNr) {
file.write(e)
e := ""
}
}