Alternative notification system. Quickest way to read/write to a file?

Hi all!

I am setting up a modified notification system, which write on my screen the messages I want.
(The differences with respect to “display notification” is that I can decide for how long the messages are visible.
See example using this link:
https://imgur.com/dJzokMz

It is based on Übersicht Widgets.
I am relaying in writing/reading stored infos on a text file.
The handler I use is the “classical one”

on WriteTotextFile(fAlias, textToWrite)
	try
		open for access (fAlias) with write permission
		set eof fAlias to 0
		write textToWrite to fAlias -- file the_path
		close access fAlias -- (file the_path)
	on error
		close access fAlias --  (file the_path)
	end try
end WriteTotextFile

But I find it a bit too slow for my needs.

Which is the fasted way to write/read from a .txt file?

How fast is fast enough? That handler takes about 0.004 seconds here, and if you know the file exists and not already open you can string it down to just the set eof and write lines, which comes in about 0.003 seconds.

Using ASObjC brings it down to under 0.002 seconds.

But if 0.004 is too slow, 0.002 might still not be fast enough.

It might go a smidgen faster if you use the access reference instead of the file or alias specifier:

on WriteTotextFile(fAlias, textToWrite)
	try
		set fRef to (open for access (fAlias) with write permission)
		set eof fRef to 0
		write textToWrite to fRef -- file the_path
		close access fRef -- (file the_path)
	on error
		close access fRef --  (file the_path)
	end try
end WriteTotextFile

I’ve always saved stuff to a text file pretty much as the OP shows in Post 1. Do I understand correctly that I can safely use the following code if the file in question is not open? The text to be saved is a single line which overwrites existing text in the file. Thanks.

set preferenceFile to "Macintosh HD:Users:peavine:Working:Preference File.txt" as alias
set thePreference to "Preference One"

writePreference(preferenceFile, thePreference)

on writePreference(preferenceFile, thePreference)
	set eof preferenceFile to 0
	write thePreference to preferenceFile
end writePreference

BTW, I assume that the fastest way to read a text file separate and distinct from writing the text file is:

set preferenceFile to "Macintosh HD:Users:peavine:Working:Preference File.txt" as alias

set preferenceFileText to paragraphs of (read preferenceFile)

If my memory is right, it seems that there is a typo in all the posted codes.

They contain:

set eof something to 0

when it’s supposed to be

set eof of something to 0

Ooops. It’s just that, for years, I wasted three chars in many of my scripts.

Yvan KOENIG running High Sierra 10.13.6 in French (VALLAURIS, France) dimanche 15 décembre 2019 16:58:36

That code works if the file both exists and is not already open. It’s the assumption that it’s not already open that’s not particularly safe in a script which cuts corners like that. :wink: If the file’s not already open, both commands in the handler will open and close their own access channels, so speedwise, it’s a balancing act between that and opening a single channel once, executing the two commands, and closing the channel once. If the file is already open, the code will only work if the file’s open with write permission and the script happens to get the channel which has the write permission.

Yes. It’s one of those cases where the syntax can fool you. :slight_smile: The line isn’t ‘set’-ing the ‘eof’ property of something to 0. It’s a command called ‘set eof’ whose direct parameter is something and labelled parameter ‘to’ is 0.

But putting ‘of’ after the command appears to be harmless, much as it is after ‘count’.

Thanks Nigel. I enjoy learning the behind-the-scenes (balancing-act) stuff that can affect the speed and operation of a script.

I retested the two write alternatives using Script Geek and the cut-the-corners script was about two milliseconds faster on my computer. I suspect that’s not noticeable to the user and, in any event, I try to place text-file writing at the end of the script when all user interaction is done. And, of course, reliability should come first.

A smidgen indeed. Somewhat surprisingly, at least to me, I gain another smidgen by writing “as «class utf8»” rather than leaving it at MacRoman.

FWIW, here’s the ASObjC version:

on WriteTotextFile(posixPath, textToWrite)
	set theText to current application's NSString's stringWithString:textToWrite
	return theText's writeToFile:posixPath atomically:true encoding:4 |error|:(missing value) -- NSUTF8StringEncoding
end WriteTotextFile

Here, it’s a shade over twice as fast. It also doesn’t care if you’ve left the file open, although obviously if you do that often enough, you’ll run out of file handles.

Thanks a lot to all for the feedback.
I am sure this will help. Moreover it is good to learn new things.

Just to report uniform results from one computer, I reran a number of scripts from above in Script Geek and received the results shown below. They are pretty much as expected.

Nigel Full Write Script from Post 3 - 3.3 ms

Peavine Corner-Cutting Write Script from Post 4 - 2.3 ms

Shane ASObjC Write Script from Post 10 - 1.4 ms

Peavine Read-Only Script from Post 4 - 1.3 ms

As I am curious, I tested my old handler ( minus two parameters) and the ASObjC one.
The test script read a file containing 3 182 570 bytes
then it executed 1000 times the old handler
then it executed 1000 times the ASObjC handler.
I was surprised to see that the old beast was - a very little bit - faster than the new one.

----------------------------------------------------------------
use AppleScript version "2.5"
use framework "Foundation"
use scripting additions
----------------------------------------------------------------

on WriteTotextFile(posixPath, textToWrite)
	set theText to current application's NSString's stringWithString:textToWrite
	return theText's writeToFile:posixPath atomically:true encoding:4 |error|:(missing value) -- NSUTF8StringEncoding
end WriteTotextFile

(*
Handler borrowed to Regulus6633 - http://macscripter.net/viewtopic.php?id=36861
*)
on writeto(targetFile, theData)
	-- targetFile is the file you want to write as «class furl»
	-- theData is the data you want in the file.
	try
		set openFile to open for access targetFile with write permission
		set eof openFile to 0
		write theData to openFile starting at eof as «class utf8»
		close access openFile
		return true
	on error
		try
			close access targetFile
		end try
		return false
	end try
end writeto

#=====

set p2d to path to desktop as text

set theSource to (p2d & "Titres m4p 20191215.txt") as alias
set textToWrite to read theSource as «class utf8» -- 3 182 570 bytes

# for old handler
set targetFile to (p2d & "with old handler.txt") as «class furl» # doesn't exist on entry
# for ASObjC handler
set posixPath to POSIX path of (p2d & "with ASObjC.txt") # doesn't exist on entry

set startTime to (time of (current date))
repeat 1000 times
	my writeto(targetFile, textToWrite)
end repeat
set endTime1 to (time of (current date))

repeat 1000 times
	my WriteTotextFile(posixPath, textToWrite)
end repeat
set endTime2 to (time of (current date))
return {endTime1 - startTime, endTime2 - endTime1} --> {38, 44}

Yvan KOENIG running High Sierra 10.13.6 in French (VALLAURIS, France) lundi 16 décembre 2019 17:35:14

I did it twice and get {64, 19} with a “2.534.599 bytes (3,1 MB on disk)” . I might do something wrong …
or maybe the newest handler is optimised for the architecture of new Macs, which in my case is:
iMac (Retina 5K, 27-inch, 2019)
Processo 3,6 GHz 8-Core Intel Core i9
memory 32 GB 2667 MHz DDR4
L.

PS: Just run it again with: 63/19
PS2: I only included a “beep” between the two 1000x repeat loops to receive a feedback when one loop was finished, and the other was starting. Which was confirmed by the appearance of the text file(s) on my desktop.

Thank’s. Interesting to know that.

My machine is :
iMac (21.5 pouces, mi-2011)
Processor 2,8 GHz Intel Core i7
memory 16 Go 1333 MHz DDR3
boot volume: external Thunderbolt 1 SSD with Trim active
running 10.13.6 ( can’t run newer )

I’m surprised by the fact that the old handler is more than two times slower on your machine than on mine.

I guess that your internal SSD is faster than my internal one.
Here Blackmagic Disk Test report:
write : 340 MB/s
read : 380 MB/s
but I doubt that this explain the difference.
I assume that the ASObjC internal routines are enhanced in modern machines ( or in modern OS ).
After all, it’s the engineers’s duty, isn’t it ?

Yvan KOENIG running High Sierra 10.13.6 in French (VALLAURIS, France) lundi 16 décembre 2019 19:04:49

The quickest way to read/write data to/from a file is creating RAM disk. (That is, when you want read/write to a file multiple times.)

  1. Create RAM disk on your Mac programatically:

-- Created 2017-01-24 by Takaaki Naganoya
-- 2017 Piyomaru Software
--https://www.tekrevue.com/tip/how-to-create-a-4gbs-ram-disk-in-mac-os-x/

set dName to "RAM Disk"
set dCapacity to 512 * 4 * 1024 --1GB
set aCmd to "diskutil erasevolume HFS+ '" & dName & "' `hdiutil attach -nomount ram://" & (dCapacity as string) & "`"
try
	do shell script aCmd
end try
  1. Do write/read operations, as with usual disk, but much faster.

  2. Tell to Finder to eject it, when complete read/write jobs. (Before this, if is need, move the last file from RAM and store on hard disk.)

I apologizes but using a Ram disk isn’t always a valid scheme.
Often, the 16 Gb of my iMac are quite entirely used so Numbers is forced to use SWAP so it works slowly.
If I eat a block of memory with a Ram disk the situation will be worse.
The 16 Gbytes are made of 4 units of 4 Gbytes which were the only size available when I bought the machine.
Of course I would be able to drop that available 16 Gbytes and upgrade to 32 Gbytes but this means buy 32 Gbytes of Ram (180€) for a 8 years old machine. From my point of view, it’s not reasonable.

Yvan KOENIG running High Sierra 10.13.6 in French (VALLAURIS, France) lundi 16 décembre 2019 21:41:28

A couple more timings, for 1000 iterations with a shortish string.

  • Using write command, with try block and using file ref: ~3.8 secs
  • Using ASObjC write: ~1.7 secs
  • Using a simple Objective-C loop: ~0.5 secs

So in terms of actual time per individual write, AppleScript isn’t really too badly off.

Hmm…I don’t think the bottleneck is only writing log file.
If you stopped to wrinting log, you can figure out how long does it take to write log to text file.

(1) Frequency of writing to log (to display)
If frequency is high (each file), the total processing time takes long time. You’d better to write log every 10 files, I think.
Or, display “not Normal” status log.

(2)Which is the true bottleneck?
The whole workflow seems to be a ePub checking with ePubchecker (and its alternatives).
Disk performance seems not so bad.
How about CPU performance? CPU thermo?

I made complementary tests with different file sizes.
I got:
→ 4 000 bytes → {1, 1}
→ 8 000 bytes → {1, 1}
→ 16 000 bytes → {1, 1} or {2, 1}
→ 32 000 bytes → {1, 1} or {2, 1}
→ 64 000 bytes → {1, 1} or {2, 1}
→ 256 000 bytes → {4, 2} or {3, 3}
→ 512 000 bytes → {6, 4} or {5, 5}
→ 1 024 000 bytes → {11, 8} on every attempt
→ 2 048 000 bytes → {21, 16} or {22, 18}
→ 3 072 000 bytes → {37, 28} or {33, 33} or {31, 33}

Yvan KOENIG running High Sierra 10.13.6 in French (VALLAURIS, France) mardi 17 décembre 2019 15:47:43