## 4 Experiments and Results
### Results
#### 4.2.1 Image Classification as Batch Processing
The limited speedup observed in the fog layer, in contrast to the edge layer, can be attributed to the virtual nature of the compute instances and the network infrastructure connecting the virtual VM, characterized by a virtual private network. Spark's configuration parameters, including the executor memory and executor cores, are key parameters in terms of optimizing the performance of applications. The executor memory is the amount of memory allocated to each Spark executor node, and the executor cores represent the number of CPU cores allocated to each executor node, which influences the parallelism and concurrency of the tasks. The executor memory and cores can be adjusted based on the nature of the workload, ensuring that the Spark application performs optimally under varying conditions. As Spark's performance in the cloud and in clusters of computers has been extensively studied [73; 74], we focused on the most resource-constrained device and performed detailed experiments on the edge-layer Spark cluster. In this experiment, we set the number of executor nodes to four and the memory size to  
Figure 3: Impact of the number of nodes on performance. 6 GB. We measured the execution (training) time by varying the number of cores in each worker node from one to four. As shown in Figure 4, increasing the number of cores in each executor significantly increased the performance. Compared with our obtained results shown in Figure 3 and reported in this section, we can observe that training on one node with four cores took 3690 s, and training with four nodes with one core took 2467 s to complete. Training on one node had a longer execution time than with one core each and four nodes under the same executor memory size, which highlights the promising results obtained from the collaborative power of local computing devices when training a machine learning task in a distributed and collaborative way. To investigate the impact of the executor memory size on the performance, we measured the training time by varying the memory size of the Spark executor from 1 GB to 6 GB. We set the number of worker nodes to four, and the number of executor cores per node was four. The execution was repeated five times, and in the graph, it is plotted as the average value. As shown in Figure 5, increasing the size of the memory in each executor node did not speed up the execution. In addition, we found that execution with 2G memory took longer than with 1G, and execution with 5G memory took longer than with 4G. Although the image classification application was trained for 1000 iterations using the MLP model, this pattern was not caused by Spark image classification tasks. We observed the memory utilization in five-second intervals using the SAR (system activity report) command in Linux and confirmed that the actual memory utilization in all Spark worker nodes consistently remained below 1 GB throughout the entire execution period. Please note that the parallel training of MNIST classification tasks on Spark worker nodes does not fully consume all allocated memory, and the excessive allocation of memory beyond the necessary amount does not bring about benefits in terms of performance. Thus, we can conclude that increasing the Spark executor memory size cannot guarantee a speedup in a distributed environment. However, the current analysis, we did not specifically incorporate memory-intensive data analytic tasks. The observed patterns in Figure 5 primarily arose from the execution of the image classification training application, and as mentioned above, the actual memory utilization during this execution remained below 1 GB. If we execute memory-intensive data analytic tasks, changing the memory size impacts the system performance. This is particularly important in resource-constrained edge environments, where resources are limited and need to be carefully managed. Figure 4: Impact of executor cores on performance.