02-28-2025 11:22 AM
@Thomas_robertson wrote:
- The safe way to pass a map around the software is to use a DVR.
Out of curiosity, do Sets behave the same way? I am not displaying any sets on the Front Panel, but they are in a shift register for subVIs to access. I changed my Map Access to a DVR of a Map instead of passing the Map around the shift register, not just wondering if I need to do the same for Sets?
03-02-2025 06:53 AM - edited 03-02-2025 06:54 AM
Sets and Maps are complex datatypes, but are treated like any other LabVIEW data: By Value. They are very efficient and there is no problem with them, EXCEPT when you create copies. And fundamentally, everytime you branch a wire, there is potentially created a copy. The LabVIEW compiler tries to minimize copies when it can, but if you wire a data type out of a subVI and keep a copy in the subVI (in a shift register or feedback node, the magic ends exactly here. There is simply no way to try to optimize that further in a meaningful way without using full level application optimization that would require more resources than your normal PC ever will have.
Basically, any wire branch of complex data is a potential performance hazard. LabVIEW may be able to optimize it if it is inside a single diagram level, but simply has to stop at some point to try to be smarter, to avoid crippling compile time performance for a bit more runtime performance.,
03-02-2025 02:25 PM
@rolfk wrote:
Sets and Maps are complex datatypes, but are treated like any other LabVIEW data: By Value. They are very efficient...
Yes, I agree. By the way, I'm positively impressed with the performance of the maps in LV. Just for fun, I tested maps from Rust (both HashMap and BTree), and yes, they are faster than LabVIEW, but not dramatically:
Code used for benchmark:
use std::collections::{HashMap, BTreeMap};
use std::slice;
//LabVIEW String Handle:
#[repr(C)]
pub struct LStr {
cnt: i32,
str: [u8; 1],
}
type LStrHandle = *mut *mut LStr;
// ============================================================================
// Hash Map
//
#[unsafe(no_mangle)]
pub unsafe extern "C" fn hash_map_init(hash_map_out: *mut *mut HashMap<Vec<u8>, i32>) -> i32 {
if hash_map_out.is_null() { return -1; } // Error: null output pointer
let hash_map = HashMap::new();
unsafe { *hash_map_out = Box::into_raw(Box::new(hash_map)); }
0
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn hash_map_insert(hash_map_in: *mut HashMap<Vec<u8>, i32>, key: LStrHandle, value: i32) -> i32 {
if hash_map_in.is_null() || key.is_null() { return -1; } // Error: null input pointer
unsafe {
let key_ptr = *key; // Dereference the double pointer to get to LStr
if key_ptr.is_null() { return -1; } // Error: null pointer
let key_ref = &*key_ptr;
if key_ref.cnt <= 0 { return -2; } // Error: invalid string length
// Get the string bytes using the count from the struct
let str_ptr = key_ref.str.as_ptr();
let bytes = slice::from_raw_parts(str_ptr, key_ref.cnt as usize);
(*hash_map_in).insert(bytes.to_vec(), value); // Create Vec<u8> from bytes
0
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn hash_map_get(hash_map_in: *mut HashMap<Vec<u8>, i32>, key: LStrHandle, value_out: *mut i32) -> i32 {
if hash_map_in.is_null() || key.is_null() || value_out.is_null() { return -1; } // Error: null pointers
unsafe {
// Dereference the double pointer to get to LStr
let key_ptr = *key;
if key_ptr.is_null() { return -1; } // Error: null pointer
let key_ref = &*key_ptr;
if key_ref.cnt <= 0 { return -2; } // Error: invalid string length
// Get the string bytes using the count from the struct
let str_ptr = key_ref.str.as_ptr();
let bytes = slice::from_raw_parts(str_ptr, key_ref.cnt as usize);
if let Some(v) = (*hash_map_in).get(bytes) {
*value_out = *v;
0
} else {
-4 // Error: key not found
}
}
}
// ============================================================================
// BTree Map
//
#[unsafe(no_mangle)]
pub unsafe extern "C" fn btree_map_init(btree_map_out: *mut *mut BTreeMap<Vec<u8>, i32>) -> i32 {
if btree_map_out.is_null() { return -1; } // Error: null output pointer
let btree_map = BTreeMap::new();
unsafe { *btree_map_out = Box::into_raw(Box::new(btree_map)); }
0
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn btree_map_insert(btree_map_in: *mut BTreeMap<Vec<u8>, i32>, key: LStrHandle, value: i32) -> i32 {
if btree_map_in.is_null() || key.is_null() { return -1; } // Error: null input pointer
unsafe {
let key_ptr = *key;
if key_ptr.is_null() { return -1; }
let key_ref = &*key_ptr;
if key_ref.cnt <= 0 { return -2; }
let str_ptr = key_ref.str.as_ptr();
let bytes = slice::from_raw_parts(str_ptr, key_ref.cnt as usize);
(*btree_map_in).insert(bytes.to_vec(), value);
0
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn btree_map_get(btree_map_in: *mut BTreeMap<Vec<u8>, i32>, key: LStrHandle, value_out: *mut i32) -> i32 {
if btree_map_in.is_null() || key.is_null() || value_out.is_null() { return -1; }
unsafe {
let key_ptr = *key;
if key_ptr.is_null() { return -1; }
let key_ref = &*key_ptr;
if key_ref.cnt <= 0 { return -2; }
let str_ptr = key_ref.str.as_ptr();
let bytes = slice::from_raw_parts(str_ptr, key_ref.cnt as usize);
if let Some(v) = (*btree_map_in).get(bytes) {
*value_out = *v;
0
} else {
-4 // Error: key not found
}
}
}
The code in the attachment is not intended for production; it's just a funny Sunday evening experiment, nothing more. It is 64-bit only, compressed twice, otherwise, the NI Virus Scan runs forever.
03-02-2025 11:03 PM - edited 03-02-2025 11:17 PM
Sorry, I forgot about the overhead caused by functional globals. Additionally, Maps were introduced in LabVIEW 2019. Below is the correct comparison:
And the results — the performance of LabVIEW Maps (for the given data) is somewhere between HashMap and BTree:
Code for LV2019+Rust 1.85.0.
To compile Rust code, you need to install the latest version of Rust (or update your existing version), then build it as shown below:
rustup update
cargo build -r
03-05-2025 12:21 PM - edited 03-05-2025 12:24 PM
Recently our team began using Maps in a portion of our code to facilitate faster lookup of items. After implementing it in a small portion of our code we noticed an alarming 50% increase in the time to do some of our larger operations. After tracing it down to the Map code we read a bit on the forums and did some experimenting and learned some lessons which we thought we’d share:
We are grateful to this thread which helped guide us to a better solution, but I felt it was worth summarizing our findings in a fresh thread. https://forums.ni.com/t5/LabVIEW/Maps-are-too-slow/td-p/4061047.
I'm curious if others have made similar findings and if they have anything to add regarding best practices to mitigate these performance concerns?
03-05-2025 01:08 PM
Did you ever test to see if putting a Map as private data of a class had the same performance hits?
That's how I use a Map in the one application I have used it in so far. I feel confident that the "drawing the control/indicator" bit isn't a problem when using it that way, as the class icon doesn't show it and all the accessors are in-lined.
The Maps in the class I have aren't large enough to notice a hit on copying them, if that's still the case though. In theory that would only happen if the Map was taken out of the accessor and the wire forked causing a copy, but as you mention it seems that theory may not apply to maps as it ought to.
03-05-2025 01:55 PM
@Kyle97330 wrote:
The Maps in the class I have aren't large enough to notice a hit on copying them, if that's still the case though. In theory that would only happen if the Map was taken out of the accessor and the wire forked causing a copy, but as you mention it seems that theory may not apply to maps as it ought to.
Actually, anytime you branch the class wire you do usually involve a copy of all the private data in the class, so there is more potential to run into performance issues than just passing out the map out of a property accessor. The only exception to this rule is in a class method that branches the wire to access some of the private data through a Unbundle (by Name) node. LabVIEW can and will optimize such access if it is limited to one diagram level.
03-05-2025 02:44 PM
@Kyle97330 wrote:
Did you ever test to see if putting a Map as private data of a class had the same performance hits?
I had a dramatic slowdown that I solved by switching from a Map in private data to a DVR to a map in private data. (https://forums.ni.com/t5/LabVIEW/Maps-are-too-slow/m-p/4315987/highlight/true#M1262873)
There was probably a way to get the compiler to stop doing whatever copies it was doing, but switching to a DVR was very quick and easy and accomplished what I needed.